import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import threading

import pymssql
import pymysql
import os, sys
import re
from datetime import datetime
import json
from concurrent.futures import ThreadPoolExecutor, as_completed
import queue
import time
import logging
from openpyxl import Workbook

# 新增一个函数，用于获取资源的绝对路径（兼容开发环境和Nuitka打包）
def resource_path(relative_path):
    """ 获取资源的绝对路径，兼容开发环境和Nuitka打包环境 """
    if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
        # Nuitka/PyInstaller打包后的路径
        base_path = sys._MEIPASS
    else:
        # 开发环境下的路径
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

class MySQLGUITool:
    def __init__(self, root):
        self.root = root
        self.root.title("MySQL批量执行工具")
        self.root.geometry("1200x840")
        self.root.resizable(True, True)

        # 使用新的resource_path函数来获取图标路径
        try:
            icon_path = resource_path('mysql.ico')
            self.root.iconbitmap(icon_path)
        except Exception as e:
            print(f"警告：无法设置窗口图标: {e}")

        # 多线程相关属性
        self.max_threads = 10  # 最大线程数
        self.thread_pool = None  # 线程池
        self.is_executing = False  # 执行状态标志
        self.cancel_event = threading.Event()  # 取消事件
        self.result_queue = queue.Queue()  # 结果队列

        # 数据库连接参数
        self.db_app = tk.StringVar()  # 数据库应用
        self.db_name = tk.StringVar(value="station_gantry")
        self.db_user = tk.StringVar(value="root")
        self.db_password = tk.StringVar(value="kH7@S#2m")
        self.script_file = tk.StringVar(value="script.sql")

        # 服务器源库连接参数
        self.server_db_app = tk.StringVar()  # 服务器源库
        self.server_db_ip = tk.StringVar() # 服务器源库IP
        self.server_db_type = tk.StringVar()   # 数据库类型
        self.server_db_name = tk.StringVar()  # 数据库名
        self.server_db_user = tk.StringVar()  # 用户
        self.server_db_password = tk.StringVar()  # 密码
        self.server_list_sql = tk.StringVar() # 获取服务器源列表的SQL语句

        # 数据库应用列表和配置
        self.db_types = []
        self.database_configs = []

        # 服务器源库配置（独立于数据库配置）
        self.server_db_types = []
        self.server_database_configs = []

        # SQL语句
        self.sql_text = ""

        # 加载JSON配置文件
        self.config_file = 'config.json'
        # 程序设置（新增密码掩码配置）
        self.program_settings = {
            "max_threads": 10, 
            "default_script": "script.sql",
            "default_sql": "SELECT * FROM table_name;",
            "password_mask_enabled": False,
            "merge_result_enabled": False,  # 默认不启用合并结果
            "enable_logging": False,  # 新增日志开关
            "default_save_path": "" # 新增：默认表格保存路径
        }
        
        # 初始化密码掩码控制变量（新增）
        self.password_mask_enabled = tk.BooleanVar(value=self.program_settings.get("password_mask_enabled", False))
        # 新增：合并结果复选框变量，默认值由配置决定
        self.merge_single_row = tk.BooleanVar(value=False)

        # 绑定密码掩码变量的跟踪，以便实时更新密码框显示
        self.password_mask_enabled.trace_add("write", self._update_password_mask_display)

        self.script_files = []  # 支持多脚本文件
        self.logger = logging.getLogger("MySQLTool")
        self._setup_logging()
        self.load_json_config()  # 加载配置后会覆盖默认值

        # 服务器列表和执行结果
        self.server_list = []
        self.execution_results = {}

        self.setup_ui()
        # 确保所有密码框在UI设置后更新显示
        self._update_password_mask_display()
        self._update_script_file_text_ui() # Ensure script file text is populated on startup

    def _update_script_file_text_ui(self):
        """Helper to update the main window's script file scrolledtext"""
        if hasattr(self, 'script_file_text') and self.script_file_text.winfo_exists():
            self.script_file_text.config(state="normal")
            self.script_file_text.delete(1.0, tk.END)
            for f in self.script_files:
                if f.strip():
                    self.script_file_text.insert(tk.END, f + "\n")
            self.script_file_text.config(state="disabled")

    def _setup_logging(self):
        enable_logging = self.program_settings.get("enable_logging", False)
        if enable_logging:
            logging.basicConfig(filename="mysql_tool.log", level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
            self.logger.setLevel(logging.INFO)
        else:
            self.logger.setLevel(logging.CRITICAL)

    def reload_config(self):
        self.logger.info("开始重新加载配置...")
        self.load_json_config() # This reloads data into self.program_settings, self.database_configs, self.server_database_configs

        # --- Update Main Window UI Elements ---
        # Update DB connection parameters (triggered by combo box selection change)
        if hasattr(self, 'db_type_combo') and self.db_type_combo.winfo_exists():
            current_db_app = self.db_app.get() # Get current selection
            self.db_type_combo['values'] = self.db_types # Update dropdown options
            if current_db_app in self.db_types: # Try to keep current selection if it still exists
                self.db_app.set(current_db_app)
            elif self.db_types: # Otherwise, select the first one
                self.db_app.set(self.db_types[0])
            else:
                self.db_app.set("") # Clear if no options
            self.on_db_type_changed(None) # Trigger update of other DB fields

        # Update SQL script path scrolledtext
        if hasattr(self, 'script_file_text') and self.script_file_text.winfo_exists():
            self.script_file_text.config(state="normal")
            self.script_file_text.delete(1.0, tk.END)
            # Use self.script_files which is updated by load_json_config
            for f in self.script_files:
                if f.strip(): # Avoid inserting empty lines if default_script has trailing newlines
                    self.script_file_text.insert(tk.END, f + "\n")
            self.script_file_text.config(state="disabled")

        # Update SQL editor
        if hasattr(self, 'sql_editor') and self.sql_editor.winfo_exists():
            self.sql_editor.delete(1.0, tk.END)
            self.sql_editor.insert(tk.END, self.program_settings.get('default_sql', ''))
            self.sql_text = self.sql_editor.get(1.0, tk.END).strip() # Ensure internal variable is also updated

        # Update default save path entry
        if hasattr(self, 'save_path_entry') and self.save_path_entry.winfo_exists():
            self.save_path_entry.delete(0, tk.END)
            self.save_path_entry.insert(0, self.program_settings.get('default_save_path', ''))

        # Update server list textbox
        if hasattr(self, 'server_text') and self.server_text.winfo_exists():
            self.server_text.delete(1.0, tk.END)
            self.server_text.insert(tk.END, self.program_settings.get('default_servers', ''))
            self.parse_servers() # Re-parse servers to update server_count_var

        # Update default save path
        if hasattr(self, 'save_excel_path_var') and isinstance(self.save_excel_path_var, tk.StringVar):
            self.save_excel_path_var.set(self.program_settings.get('default_save_path', ''))

        # Update Server Source DB parameters (triggered by combo box selection change)
        if hasattr(self, 'server_db_combo') and self.server_db_combo.winfo_exists():
            current_server_db_app = self.server_db_app.get() # Get current selection
            self.server_db_combo['values'] = self.server_db_types # Update dropdown options
            if current_server_db_app in self.server_db_types: # Try to keep current selection if it still exists
                self.server_db_app.set(current_server_db_app)
            elif self.server_db_types: # Otherwise, select the first one
                self.server_db_app.set(self.server_db_types[0])
            else:
                self.server_db_app.set("") # Clear if no options
            self.on_server_db_changed(None) # Trigger update of other Server Source DB fields


        # --- Update Config Window UI Elements (if open) ---
        if hasattr(self, 'config_window') and self.config_window.winfo_exists():
            # Update program settings
            self.config_max_threads.set(self.program_settings.get('max_threads', 10))
            
            self.config_merge_result_var.set(self.program_settings.get('merge_result_enabled', False))
            self.password_mask_enabled.set(self.program_settings.get('password_mask_enabled', False)) # This will also trigger _update_password_mask_display() implicitly for treeviews and password entries
            self.config_enable_logging.set(self.program_settings.get('enable_logging', False))

            # Update default script path in config window
            if hasattr(self, 'config_script_path') and self.config_script_path.winfo_exists():
                self.config_script_path.config(state="normal")
                self.config_script_path.delete(1.0, tk.END)
                self.config_script_path.insert(tk.END, self.program_settings.get('default_script', ''))
                self.config_script_path.config(state="disabled")

            # Update default SQL in config window
            if hasattr(self, 'config_default_sql') and self.config_default_sql.winfo_exists():
                self.config_default_sql.delete(1.0, tk.END)
                self.config_default_sql.insert(tk.END, self.program_settings.get('default_sql', ''))

            # Update default servers in config window
            if hasattr(self, 'config_default_servers') and self.config_default_servers.winfo_exists():
                self.config_default_servers.delete(1.0, tk.END)
                self.config_default_servers.insert(tk.END, self.program_settings.get('default_servers', ''))

            # Explicitly reload treeviews for robustness, in case _update_password_mask_display() didn't cover all scenarios
            if hasattr(self, 'db_config_tree') and self.db_config_tree.winfo_exists():
                self.load_db_config_to_tree()
            if hasattr(self, 'server_db_config_tree') and self.server_db_config_tree.winfo_exists():
                self.load_server_db_config_to_tree()

        # Re-apply logging configuration
        self._setup_logging()
        
        self.logger.info("配置已重新加载完成")
        messagebox.showinfo("提示", "配置已重新加载")

    def setup_ui(self):
        """设置用户界面"""
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="8")
        main_frame.grid(row=0, column=0, sticky="nsew")

        # 优化行列权重分配
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=0)  # 左侧区域固定宽度
        main_frame.columnconfigure(1, weight=1)  # 右侧区域可扩展
        main_frame.rowconfigure(0, weight=0)  # 数据库配置区域 - 固定高度
        main_frame.rowconfigure(1, weight=0)  # SQL脚本和语句区域 - 固定高度
        main_frame.rowconfigure(3, weight=1)  # 服务器和结果区域 - 可扩展

        # 1. 数据库连接配置区域
        self.create_db_config_section(main_frame)

        # 2. SQL脚本选择区域
        self.create_script_section(main_frame)

        # 3. SQL语句区域
        self.create_sql_section(main_frame)

        # 4. 服务器列表输入区域
        self.create_server_input_section(main_frame)

        # 5. 结果显示表格
        self.create_results_table(main_frame)

        # 6. 状态栏
        self.create_status_bar()

    def create_db_config_section(self, parent):
        """创建数据库配置区域"""
        # 数据库配置框架
        db_frame = ttk.LabelFrame(parent, text="数据库连接配置", padding="8")
        db_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 8))
        db_frame.columnconfigure(1, weight=1)
        db_frame.columnconfigure(3, weight=1)
        db_frame.columnconfigure(5, weight=1)
        db_frame.columnconfigure(7, weight=1)

        # 数据库应用
        ttk.Label(db_frame, text="数据库应用:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
        self.db_type_combo = ttk.Combobox(db_frame, textvariable=self.db_app, width=15, state="readonly")
        self.db_type_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 20))
        self.db_type_combo['values'] = self.db_types
        if self.db_types:
            self.db_type_combo.current(0)
        self.db_type_combo.bind("<<ComboboxSelected>>", self.on_db_type_changed)

        # 数据库名称
        ttk.Label(db_frame, text="数据库名称:").grid(row=0, column=2, sticky=tk.W, padx=(0, 5))
        db_name_entry = ttk.Entry(db_frame, textvariable=self.db_name, width=15)
        db_name_entry.grid(row=0, column=3, sticky=(tk.W, tk.E), padx=(0, 20))
        db_name_entry.bind('<FocusOut>', lambda e: self._update_db_name())

        # 用户名
        ttk.Label(db_frame, text="用户名:").grid(row=0, column=4, sticky=tk.W, padx=(0, 5))
        db_user_entry = ttk.Entry(db_frame, textvariable=self.db_user, width=15)
        db_user_entry.grid(row=0, column=5, sticky=(tk.W, tk.E), padx=(0, 20))
        db_user_entry.bind('<FocusOut>', lambda e: self._update_db_user())

        # 密码（修改show参数）
        ttk.Label(db_frame, text="密码:").grid(row=0, column=6, sticky=tk.W, padx=(0, 5))
        self.db_password_entry = ttk.Entry(
            db_frame,
            textvariable=self.db_password,
            width=15,
            show="*" if self.password_mask_enabled.get() else ""  # 动态控制
        )
        self.db_password_entry.grid(row=0, column=7, sticky=(tk.W, tk.E), padx=(0, 20))
        self.db_password_entry.bind('<FocusOut>', lambda e: self._update_db_password())

        # 测试连接按钮
        self.test_btn = ttk.Button(db_frame, text="测试连接", command=self.test_connections)
        self.test_btn.grid(row=0, column=8, padx=(10, 0))

        # 添加线程数配置
        ttk.Label(db_frame, text="并发数:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=(3, 0))
        self.thread_count_var = tk.IntVar(value=self.max_threads)
        thread_spinbox = ttk.Spinbox(db_frame, from_=1, to=50, textvariable=self.thread_count_var, width=10)
        thread_spinbox.grid(row=1, column=1, sticky=tk.W, pady=(3, 0))

        # 停止按钮
        self.stop_btn = ttk.Button(db_frame, text="停止执行", command=self.stop_execution, state="disabled")
        self.stop_btn.grid(row=1, column=2, padx=(10, 0), pady=(3, 0))

        # 重新加载配置按钮（放在配置按钮前面）
        self.reload_btn = ttk.Button(db_frame, text="重新加载配置", command=self.reload_config)
        self.reload_btn.grid(row=1, column=7, padx=(10, 0), pady=(3, 0))
        # 添加配置按钮
        ttk.Button(db_frame, text="配置", command=self.show_config_window).grid(row=1, column=8, padx=(10, 0), pady=(3, 0))

    def create_script_section(self, parent):
        """创建SQL脚本选择区域"""
        script_frame = ttk.LabelFrame(parent, text="SQL脚本文件", padding="8")
        script_frame.grid(row=1, column=0, sticky="nsew", pady=(0, 8), padx=(4, 1))
        
        # 允许文本区域行（row 1）垂直扩展
        script_frame.rowconfigure(1, weight=1)
        # 配置列：列0用于标签，列1用于浏览按钮，列2用于执行按钮，并允许列2拉伸以对齐右侧
        script_frame.columnconfigure(0, weight=0) # 标签列 - 不拉伸
        script_frame.columnconfigure(1, weight=0) # 浏览按钮列 - 不拉伸
        script_frame.columnconfigure(2, weight=1) # 执行按钮列，并允许其拉伸以推开右侧内容        

        # 第一行：ScrolledText 富文本框，横跨所有逻辑列
        self.script_file_text = scrolledtext.ScrolledText(script_frame, height=6, width=40, state="disabled", wrap=tk.WORD)
        self.script_file_text.grid(row=0, column=0, columnspan=3, sticky="nsew", padx=(0, 1), pady=(3,0))

        # 第二行："脚本文件:" 标签, "浏览" 按钮, "执行脚本" 按钮
        # ttk.Label(script_frame, text="脚本文件:").grid(row=1, column=0, sticky=tk.W, padx=(0, 1))
        ttk.Button(script_frame, text="浏览", command=self.browse_script_file).grid(row=1, column=1, padx=(0, 5))
        self.execute_btn = ttk.Button(script_frame, text="执行脚本", command=self.execute_scripts)
        self.execute_btn.grid(row=1, column=2, sticky=tk.W, pady=(1, 0)) # Sticky W 确保左对齐

    def create_sql_section(self, parent):
        """创建SQL语句区域"""
        sql_frame = ttk.LabelFrame(parent, text="SQL语句", padding="8")
        sql_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 8), padx=(4, 0))
        sql_frame.columnconfigure(1, weight=3)
        sql_frame.columnconfigure(2, weight=1)
        sql_frame.rowconfigure(1, weight=1)

        # SQL语句输入区域
        
        # 让按钮和复选框并排
        sql_frame.grid_columnconfigure(1, weight=1)
        sql_frame.grid_columnconfigure(2, weight=1) # Ensure execute_sql_btn has some weight
        sql_frame.grid_columnconfigure(3, weight=1)
        sql_frame.grid_columnconfigure(4, weight=1)
        sql_frame.grid_columnconfigure(5, weight=1)

        self.sql_editor = scrolledtext.ScrolledText(sql_frame, width=45, height=6, wrap=tk.WORD)
        self.sql_editor.grid(row=0, column=0, columnspan=6, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(3, 5))
        self.sql_editor.bind('<FocusOut>', lambda e: self._update_sql_text())

        # 加载默认SQL语句
        default_sql = self.program_settings.get('default_sql', 'SELECT * FROM table_name;')
        self.sql_editor.insert(tk.END, default_sql)

        # ttk.Label(sql_frame, text="SQL语句:").grid(row=0, column=0, sticky=tk.NW, padx=(0, 5), pady=(0, 3))
        # 新增：合并结果复选框
        self.merge_checkbox = ttk.Checkbutton(sql_frame, text="合并结果", variable=self.merge_single_row)
        self.merge_checkbox.grid(row=1, column=0, sticky=tk.W, padx=(5, 0), pady=(5, 5))
        self.execute_sql_btn = ttk.Button(sql_frame, text="执行SQL", command=self.execute_sql)
        self.execute_sql_btn.grid(row=1, column=1, sticky=tk.NE, pady=(5, 5))

        # 新增：保存表格相关控件
        ttk.Label(sql_frame, text="保存表格:").grid(row=1, column=2, sticky=tk.NE, padx=(3, 0), pady=(5, 5))
        self.save_excel_path_var = tk.StringVar()
        self.save_excel_entry = ttk.Entry(sql_frame, textvariable=self.save_excel_path_var, width=50)
        self.save_excel_entry.grid(row=1, column=3, sticky=tk.NE, padx=(2, 0), pady=(5, 5))
        self.browse_excel_btn = ttk.Button(sql_frame, text="浏览", command=self.browse_save_excel_path)
        self.browse_excel_btn.grid(row=1, column=4, sticky=tk.NE, padx=(2, 0), pady=(5, 5))
        self.save_excel_btn = ttk.Button(sql_frame, text="保存结果", command=self.save_sql_results_to_excel)
        self.save_excel_btn.grid(row=1, column=5, sticky=tk.NE, padx=(2, 0), pady=(5, 5))



    def create_server_input_section(self, parent):
        """创建服务器列表输入区域"""
        server_frame = ttk.LabelFrame(parent, text="MySQL服务器列表", padding="8")
        server_frame.grid(row=3, column=0, sticky="nsew", pady=(0, 8), padx=(0, 4))
        server_frame.columnconfigure(0, weight=1)
        # 关键修改：设置 server_text 所在行（row=8）的权重为1，允许高度扩展
        server_frame.rowconfigure(8, weight=1)

        # 添加服务器源库配置区域
        config_frame = ttk.Frame(server_frame)
        config_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
        config_frame.columnconfigure(0, weight=1)
        config_frame.columnconfigure(1, weight=1)
        config_frame.columnconfigure(5, weight=1)
        config_frame.columnconfigure(7, weight=1)

        # 服务器源库
        ttk.Label(config_frame, text="服务器源库:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
        self.server_db_app = tk.StringVar()
        self.server_db_combo = ttk.Combobox(config_frame, textvariable=self.server_db_app, width=25, state="readonly")
        self.server_db_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
        self.server_db_combo['values'] = self.server_db_types
        if self.server_db_types:
            self.server_db_combo.current(0)
        self.server_db_combo.bind("<<ComboboxSelected>>", self.on_server_db_changed)

        # 数据库类型
        ttk.Label(config_frame, text="数据库类型:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5))
        self.server_db_type_combo = ttk.Combobox(config_frame, textvariable=self.server_db_type, width=25, state="readonly")
        self.server_db_type_combo.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
        self.server_db_type_combo['values'] = ('MySQL', 'SQL Server')
        self.server_db_type_combo.current(0)  # 默认选择MySQL
        self.server_db_type_combo.bind("<<ComboboxSelected>>", lambda e: self._update_server_db_type())

        # 数据库IP
        ttk.Label(config_frame, text="数据库IP:").grid(row=2, column=0, sticky=tk.W, padx=(0, 5))
        self.server_db_ip_entry = ttk.Entry(config_frame, textvariable=self.server_db_ip, width=25)
        self.server_db_ip_entry.grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
        self.server_db_ip_entry.bind('<FocusOut>', lambda e: self._update_server_db_ip())

        # 数据库名称
        ttk.Label(config_frame, text="数据库名:").grid(row=3, column=0, sticky=tk.W, padx=(0, 5))
        self.server_db_name_entry = ttk.Entry(config_frame, textvariable=self.server_db_name, width=25)
        self.server_db_name_entry.grid(row=3, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
        self.server_db_name_entry.bind('<FocusOut>', lambda e: self._update_server_db_name())

        # 用户名
        ttk.Label(config_frame, text="用户名:").grid(row=4, column=0, sticky=tk.W, padx=(0, 5))
        self.server_db_user_entry = ttk.Entry(config_frame, textvariable=self.server_db_user, width=25)
        self.server_db_user_entry.grid(row=4, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
        self.server_db_user_entry.bind('<FocusOut>', lambda e: self._update_server_db_user())

        # 密码
        ttk.Label(config_frame, text="密码:").grid(row=5, column=0, sticky=tk.W, padx=(0, 5))
        self.server_db_password_entry = ttk.Entry(
            config_frame,
            textvariable=self.server_db_password,
            width=25,
            show="*" if self.password_mask_enabled.get() else "" # 添加动态控制
        )
        self.server_db_password_entry.grid(row=5, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
        self.server_db_password_entry.bind('<FocusOut>', lambda e: self._update_server_db_password())

        # 获取列表SQL
        ttk.Label(config_frame, text="SQL:").grid(row=6, column=0, sticky=tk.W, padx=(0, 5))
        self.server_list_sql_entry = ttk.Entry(config_frame, textvariable=self.server_list_sql, width=25)
        self.server_list_sql_entry.grid(row=6, column=1, columnspan=3, sticky=(tk.W, tk.E), padx=(0, 10))
        self.server_list_sql_entry.bind('<FocusOut>', lambda e: self._update_server_list_sql())

        # 设置默认SQL语句
        self.server_list_sql.set("SELECT DISTINCT(stationserverip) FROM ZX_tollStation")

        # 获取服务器列表按钮
        ttk.Button(config_frame, text="获取服务器列表", command=self.get_server_list).grid(row=7, column=0, padx=(0, 0), pady=(5, 0))

        # 服务器列表文本框
        self.server_text = scrolledtext.ScrolledText(server_frame, width=35, height=10, wrap=tk.WORD)
        self.server_text.grid(row=8, column=0, sticky="nsew")

        # # 默认示例
        # self.server_text.insert(tk.END, "192.168.1.100:3306\n192.168.1.101\n192.168.1.102:3307")
        # 加载默认服务器列表
        default_servers = self.program_settings.get('default_servers', '')
        self.server_text.insert(tk.END, default_servers)

        # 初始化服务器源库配置
        if self.server_database_configs:
            default_config = self.server_database_configs[0]
            self.server_db_app.set(default_config.get('db_app', ''))
            self.server_db_type.set(default_config.get('db_type', ''))
            self.server_db_ip.set(default_config.get('db_ip', ''))
            self.server_db_name.set(default_config.get('db_name', ''))
            self.server_db_user.set(default_config.get('username', ''))
            self.server_db_password.set(default_config.get('password', ''))
            self.server_list_sql.set(default_config.get('list_sql', ''))

    def create_results_table(self, parent):
        # SQL执行结果显示区域
        result_frame = ttk.Frame(parent)
        result_frame.grid(row=3, column=1, columnspan=4, sticky="nsew", pady=(0, 8))
        # 关键修改：设置 result_frame 的行权重，让Notebook扩展
        result_frame.columnconfigure(0, weight=1)
        result_frame.rowconfigure(0, weight=1)  # Notebook 区域可扩展
        result_frame.rowconfigure(1, weight=0)  # 执行结果摘要区域固定高度

        # 创建Notebook控件用于显示多服务器结果
        self.result_notebook = ttk.Notebook(result_frame)
        self.result_notebook.grid(row=0, column=0, sticky="nsew")

        # 创建默认的"无连接"标签页
        self.create_default_result_tab()

        """创建结果显示表格"""
        results_frame = ttk.LabelFrame(result_frame, text="执行结果摘要", padding="8")
        results_frame.grid(row=1, column=0, sticky="ew") # 占满水平，高度固定
        results_frame.columnconfigure(0, weight=1)
        results_frame.rowconfigure(0, weight=1)

        # 定义列名
        columns = ('服务器', '端口', '状态', '执行时间', '错误信息')

        # 创建表格
        self.results_tree = ttk.Treeview(results_frame, columns=columns, show='headings', height=8)

        # 设置列标题和宽度
        self.results_tree.heading('服务器', text='服务器地址')
        self.results_tree.heading('端口', text='端口')
        self.results_tree.heading('状态', text='执行状态')
        self.results_tree.heading('执行时间', text='执行时间')
        self.results_tree.heading('错误信息', text='错误信息')

        self.results_tree.column('服务器', width=120)
        self.results_tree.column('端口', width=60)
        self.results_tree.column('状态', width=80)
        self.results_tree.column('执行时间', width=150)
        self.results_tree.column('错误信息', width=300)

        # 添加滚动条
        scrollbar = ttk.Scrollbar(results_frame, orient=tk.VERTICAL, command=self.results_tree.yview)
        self.results_tree.configure(yscrollcommand=scrollbar.set)

        self.results_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))

        # 添加进度条
        self.progress_frame = ttk.Frame(results_frame)
        self.progress_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(5, 0))

        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(self.progress_frame, variable=self.progress_var, maximum=100)
        self.progress_bar.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
        self.progress_frame.columnconfigure(0, weight=1)

        self.progress_label = ttk.Label(self.progress_frame, text="就绪")
        self.progress_label.grid(row=0, column=1)

    def create_status_bar(self):
        """创建状态栏"""
        self.status_frame = ttk.Frame(self.root)
        self.status_frame.grid(row=4, column=0, columnspan=2, sticky="ew")

        # 服务器数量统计
        self.server_count_var = tk.StringVar(value="服务器: 0")
        ttk.Label(self.status_frame, textvariable=self.server_count_var).grid(row=0, column=0, padx=5, sticky="w")

        # 执行状态统计
        self.exec_status_var = tk.StringVar(value="执行状态: 成功 0 / 失败 0")
        ttk.Label(self.status_frame, textvariable=self.exec_status_var).grid(row=0, column=1, padx=5, sticky="w")

        # 配置网格权重
        self.status_frame.columnconfigure(0, weight=1)
        self.status_frame.columnconfigure(1, weight=1)

    def _update_db_name(self):
        """更新数据库名称变量"""
        self.db_name.set(self.db_name.get())

    def _update_db_user(self):
        """更新用户名变量"""
        self.db_user.set(self.db_user.get())

    def _update_db_password(self):
        """更新密码变量"""
        self.db_password.set(self.db_password.get())

    def _update_sql_text(self):
        """更新SQL语句变量"""
        self.sql_text = self.sql_editor.get(1.0, tk.END).strip()

    def _update_password_mask_display(self, *args):
        """根据密码掩码开关更新所有密码文本框的显示状态"""
        show_char = "*" if self.password_mask_enabled.get() else ""

        # 主界面的密码输入框
        if hasattr(self, 'db_password_entry') and self.db_password_entry.winfo_exists():
            self.db_password_entry.config(show=show_char)

        # 主界面"服务器源库"区域的密码输入框
        if hasattr(self, 'server_db_password_entry') and self.server_db_password_entry.winfo_exists():
            self.server_db_password_entry.config(show=show_char)

        # 数据库配置编辑界面的密码输入框
        if hasattr(self, 'edit_db_password') and self.edit_db_password.winfo_exists():
            self.edit_db_password.config(show=show_char)

        # 服务器源库配置编辑界面的密码输入框
        if hasattr(self, 'server_edit_password') and self.server_edit_password.winfo_exists():
            self.server_edit_password.config(show=show_char)

        # 更新数据库配置表格和服务器源库配置表格
        if hasattr(self, 'db_config_tree') and self.db_config_tree.winfo_exists():
            self.load_db_config_to_tree()
        if hasattr(self, 'server_db_config_tree') and self.server_db_config_tree.winfo_exists():
            self.load_server_db_config_to_tree()

    def browse_script_file(self):
        """浏览选择SQL脚本文件（支持多选）"""
        filenames = filedialog.askopenfilenames(
            title="选择SQL脚本文件",
            filetypes=[("SQL文件", "*.sql"), ("所有文件", "*.*")]
        )
        if filenames:
            self.script_files = list(filenames)
            # 更新富文本框
            self.script_file_text.config(state="normal")
            self.script_file_text.delete(1.0, tk.END)
            for f in self.script_files:
                self.script_file_text.insert(tk.END, f + "\n")
            self.script_file_text.config(state="disabled")
            self.script_file.set("; ".join(self.script_files))
            self.logger.info(f"选择SQL脚本文件: {self.script_files}")

    def parse_servers(self):
        """解析服务器列表"""
        server_text = self.server_text.get(1.0, tk.END).strip()
        servers = []

        for line in server_text.split('\n'):
            line = line.strip()
            if line:
                if ':' in line:
                    host, port = line.split(':', 1)
                    try:
                        port = int(port)
                    except ValueError:
                        port = 3306
                else:
                    host = line
                    port = 3306
                servers.append((host.strip(), port))

        return servers

    def test_connections(self):
        """测试数据库连接"""
        if self.is_executing:
            messagebox.showwarning("警告", "正在执行中，请先停止当前操作")
            return

        # 重置执行状态
        self.exec_status_var.set("执行状态: 成功 0 / 失败 0")
        servers = self.parse_servers()
        if not servers:
            messagebox.showwarning("警告", "请输入至少一个服务器地址")
            return

        # 清空结果表格
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)

        # 重置进度条
        self.progress_var.set(0)
        self.progress_label.config(text="正在测试连接...")

        # 更新按钮状态
        self._set_execution_state(True)

        # 启动测试线程
        threading.Thread(target=self._test_connections_concurrent, args=(servers,), daemon=True).start()

    def _update_server_db_type(self):
        """更新数据库类型变量"""
        self.server_db_type.set(self.server_db_type_combo.get())

    def _update_server_db_ip(self):
        """更新数据库IP变量"""
        self.server_db_ip.set(self.server_db_ip_entry.get())

    def _update_server_db_name(self):
        """更新数据库名称变量"""
        self.server_db_name.set(self.server_db_name_entry.get())

    def _update_server_db_user(self):
        """更新用户名变量"""
        self.server_db_user.set(self.server_db_user_entry.get())

    def _update_server_db_password(self):
        """更新密码变量"""
        self.server_db_password.set(self.server_db_password_entry.get())

    def _update_server_list_sql(self):
        """更新获取列表SQL变量"""
        self.server_list_sql.set(self.server_list_sql_entry.get())
    def _test_connections_concurrent(self, servers):
        """并发测试连接"""
        total_servers = len(servers)
        completed_count = 0
        results = [None] * total_servers  # 预分配结果数组，保持顺序

        # 重置取消事件
        self.cancel_event.clear()

        # 获取当前线程数设置
        max_workers = self.thread_count_var.get()

        try:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                self.thread_pool = executor

                # 提交所有任务，保持索引映射
                future_to_index = {executor.submit(self._test_single_connection, host, port): i
                                 for i, (host, port) in enumerate(servers)}

                # 处理完成的任务
                for future in as_completed(future_to_index):
                    if self.cancel_event.is_set():
                        break

                    index = future_to_index[future]
                    host, port = servers[index]
                    completed_count += 1

                    try:
                        result = future.result()
                        results[index] = (host, port, result['status'], result['duration'], result['error'])
                    except Exception as e:
                        results[index] = (host, port, "连接失败", "-", str(e))

                    # 更新进度
                    progress = (completed_count / total_servers) * 100
                    self.root.after(0, self._update_progress, progress, f"已测试 {completed_count}/{total_servers}")

                # 按顺序更新UI
                self.root.after(0, self._update_results_in_order, results)

        except Exception as e:
            self.root.after(0, messagebox.showerror, "错误", f"执行过程中发生错误: {e}")
        finally:
            self.thread_pool = None
            if self.cancel_event.is_set():
                self.root.after(0, self._update_progress, 100, "测试已取消")
            else:
                self.root.after(0, self._update_progress, 100, "测试完成")
            self.root.after(0, self._set_execution_state, False)

    def _test_single_connection(self, host, port):
        """测试单个连接"""
        if self.cancel_event.is_set():
            return {'status': '已取消', 'duration': '-', 'error': '用户取消'}

        try:
            start_time = datetime.now()

            # 尝试连接数据库
            connection = pymysql.connect(
                host=host,
                port=port,
                user=self.db_user.get(),
                password=self.db_password.get(),
                database=self.db_name.get(),
                connect_timeout=10
            )
            connection.close()

            end_time = datetime.now()
            duration = (end_time - start_time).total_seconds()

            return {'status': '连接成功', 'duration': f"{duration:.2f}秒", 'error': ''}

        except Exception as e:
            return {'status': '连接失败', 'duration': '-', 'error': str(e)}

    def _update_result_row(self, host, port, status, duration, error):
        """更新结果表格行"""
        self.results_tree.insert('', tk.END, values=(host, port, status, duration, error))
        # 统计执行状态
        success_count = 0
        fail_count = 0
        for item in self.results_tree.get_children():
            values = self.results_tree.item(item, 'values')
            if values and len(values) > 2:
                if "成功" in values[2]:
                    success_count += 1
                elif "失败" in values[2]:
                    fail_count += 1

        self.exec_status_var.set(f"执行状态: 成功 {success_count} / 失败 {fail_count}")

    def _update_progress(self, value, text):
        """更新进度条"""
        self.progress_var.set(value)
        self.progress_label.config(text=text)

    def execute_scripts(self):
        """执行SQL脚本（支持多文件顺序执行）"""
        if self.is_executing:
            messagebox.showwarning("警告", "正在执行中，请先停止当前操作")
            return

        self.exec_status_var.set("执行状态: 成功 0 / 失败 0")
        if not self.script_files:
            messagebox.showwarning("警告", "请先选择SQL脚本文件")
            return
        for f in self.script_files:
            if not os.path.exists(f):
                messagebox.showerror("错误", f"脚本文件不存在: {f}")
            return
        servers = self.parse_servers()
        if not servers:
            messagebox.showwarning("警告", "请输入至少一个服务器地址")
            return
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)
        self.progress_var.set(0)
        self.progress_label.config(text="正在执行脚本...")
        self._set_execution_state(True)
        threading.Thread(target=self._execute_scripts_concurrent, args=(servers,), daemon=True).start()

    def execute_sql(self):
        if self.is_executing:
            messagebox.showwarning("警告", "正在执行中，请先停止当前操作")
            return
        sql_content = self.sql_editor.get(1.0, tk.END).strip()
        if not sql_content:
            messagebox.showwarning("警告", "请输入SQL语句")
            return
        sql_statements = self._parse_sql_statements(sql_content)
        self.exec_status_var.set("执行状态: 成功 0 / 失败 0")
        servers = self.parse_servers()
        if not servers:
            messagebox.showwarning("警告", "请输入至少一个服务器地址")
            return
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)
        self.progress_var.set(0)
        self.progress_label.config(text="正在执行SQL...")
        self._set_execution_state(True)
        # 合并逻辑：merge_mode 仅由用户勾选状态决定，不考虑 SELECT 语句数量
        select_count = sum(1 for s in sql_statements if s.strip().upper().startswith('SELECT'))
        merge_mode = self.merge_single_row.get() # This determines cross-server merging
        self.logger.info(f"execute_sql: SQL statements count = {len(sql_statements)}, SELECT count = {select_count}, determined cross-server merge_mode = {merge_mode}")
        if merge_mode:
            self.update_result_tabs(["合并结果"])
        else:
            self.update_result_tabs([f"IP_{host}" for host, port in servers])
        threading.Thread(target=self._execute_sql_concurrent, args=(servers, sql_statements, merge_mode, select_count), daemon=True).start()

    def _execute_sql_concurrent(self, servers, sql_statements, merge_mode, select_count):
        """并发执行SQL语句"""
        self.logger.info(f"_execute_sql_concurrent: received cross-server merge_mode = {merge_mode}, select_count = {select_count}")
        total_servers = len(servers)
        completed_count = 0
        results = {}
        failed_servers = []  # 记录失败的服务器信息
        try:
            with ThreadPoolExecutor(max_workers=self.max_threads) as executor:
                futures = {executor.submit(self._execute_single_sql, host, port, sql_statements, merge_mode, select_count): (host, port)
                          for host, port in servers}
                for future in as_completed(futures):
                    host, port = futures[future]
                    server_key = f"{host}:{port}"
                    try:
                        columns, rows = future.result()
                        results[server_key] = (columns, rows)
                        self.logger.info(f"_execute_sql_concurrent: {server_key} returned columns: {columns}, rows_count: {len(rows)}")
                    except Exception as e:
                        results[server_key] = None
                        failed_servers.append((host, port, str(e)))  # 记录失败信息
                        self.logger.error(f"SQL执行失败: {host}:{port} {e}", exc_info=True)
                    completed_count += 1
                    progress = (completed_count / total_servers) * 100
                    self.root.after(0, self._update_progress, progress, f"已执行 {completed_count}/{total_servers}")
                if merge_mode:
                    self.root.after(0, self.display_merged_sql_results, results, failed_servers)
                    self.logger.info(f"_execute_sql_concurrent: calling display_merged_sql_results (cross-server merge)")
                else:
                    self.root.after(0, self.display_sql_results, results)
                    self.logger.info(f"_execute_sql_concurrent: calling display_sql_results (separate tabs)")
        except Exception as e:
            self.root.after(0, messagebox.showerror, "错误", f"执行过程中发生错误: {e}")
            self.logger.error(f"执行过程中发生错误: {e}", exc_info=True)
        finally:
            self.root.after(0, self._update_progress, 100, "执行完成")
            self.root.after(0, self._set_execution_state, False)

    def _execute_single_sql(self, host, port, sql_statements, checkbox_merge_mode, select_count):
        """执行单个SQL查询"""
        self.logger.info(f"_execute_single_sql for {host}:{port}: received checkbox_merge_mode = {checkbox_merge_mode}, select_count = {select_count}")
        try:
            connection = pymysql.connect(
                host=host,
                port=port,
                user=self.db_user.get(),
                password=self.db_password.get(),
                database=self.db_name.get(),
                connect_timeout=10
            )
            cursor = connection.cursor()
            
            all_processed_rows = [] # Will store either original rows or internally merged rows
            sequence_number = 0

            # Determine if internal field merging should happen (Level 1)
            # This is true if there are multiple SELECTs (as per user's new rule)
            should_do_internal_field_merge = (select_count > 1)

            # Store columns/rows for the *last* SELECT if no internal field merge is done
            last_select_columns = None
            last_select_rows = None

            for sql_content in sql_statements:
                sql_content = sql_content.strip()
                self.logger.info(f"[SQL执行] {host}:{port} SQL内容: {repr(sql_content)}")
                if not sql_content:
                    self.logger.warning(f"[SQL执行] {host}:{port} 跳过空SQL片段")
                    continue

                if sql_content.upper().startswith('SELECT'):
                    sequence_number += 1
                    cursor.execute(sql_content)
                    current_columns = [desc[0] for desc in cursor.description]
                    current_rows = cursor.fetchall()

                    if should_do_internal_field_merge:
                        # Perform Level 1 merging: merge fields into one string, add sequence number
                        for row in current_rows:
                            merged_row_data = ", ".join(str(cell) for cell in row)
                            all_processed_rows.append([sequence_number, merged_row_data])
                        self.logger.info(f"_execute_single_sql: {host}:{port} - Performing internal field merge for SELECT {sequence_number}")
                    else:
                        # No internal field merging (i.e., only 1 SELECT)
                        last_select_columns = current_columns
                        last_select_rows = current_rows
                        self.logger.info(f"_execute_single_sql: {host}:{port} - NOT performing internal field merge for SELECT {sequence_number}")
                else:
                    cursor.execute(sql_content)
                    connection.commit()

            cursor.close()
            connection.close()

            # Return format based on whether internal field merging was performed
            if should_do_internal_field_merge:
                self.logger.info(f"_execute_single_sql: Final return for {host}:{port}: internally merged. Columns: ['序号', '合并结果']")
                return ["序号", "合并结果"], all_processed_rows
            else:
                self.logger.info(f"_execute_single_sql: Final return for {host}:{port}: NOT internally merged. Columns: {last_select_columns}")
                return last_select_columns if last_select_columns is not None else [], last_select_rows if last_select_rows is not None else []

        except Exception as e:
            self.logger.error(f"执行SQL失败: {host}:{port} {e}", exc_info=True)
            raise Exception(f"执行SQL失败: {str(e)}")

    def display_sql_results(self, results_dict):
        """显示SQL执行结果，列宽自适应"""
        # 清空主结果表格
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)

        success_count = 0
        fail_count = 0

        # 收集所有列名和所有行数据
        # all_columns = []
        # all_rows_per_tab = []
        for server_ip, result_data in results_dict.items():
            host = server_ip.split(':')[0]
            tab_id = None
            for t in self.result_notebook.tabs():
                if self.result_notebook.tab(t, "text") == f"IP_{host}":
                    tab_id = t
                    break
            if not tab_id:
                self.logger.warning(f"未找到IP为 {host} 的标签页，跳过结果显示")
                continue

            tab_frame = self.result_notebook.nametowidget(tab_id)
            # 找到表格控件
            tree = None
            for widget in tab_frame.winfo_children():
                if isinstance(widget, ttk.Frame):
                    for child in widget.winfo_children():
                        if isinstance(child, ttk.Treeview):
                            tree = child
                            break
            if not tree:
                continue
            # 清除现有数据
            tree.delete(*tree.get_children())

            if result_data:
                columns, rows = result_data
                tree['columns'] = columns
                # 计算本表格的列宽
                col_widths = [len(str(col)) for col in columns]
                for row in rows:
                    for j, cell in enumerate(row):
                        col_widths[j] = max(col_widths[j], len(str(cell)))
                min_px = 60
                max_px = 500  # Adjusted max_px to prevent excessive horizontal expansion
                px_widths = [min(max(l * 6 + 20, min_px), max_px) for l in col_widths]
                for j, col in enumerate(columns):
                    tree.heading(col, text=col)
                    tree.column(col, width=px_widths[j], minwidth=40, stretch=tk.NO)
                for row in rows:
                    tree.insert('', tk.END, values=row)
                status = "执行成功"
                success_count += 1
                error_msg = ""
            else:
                status = "执行失败"
                fail_count += 1
                error_msg = "执行失败或无结果"
            # 解析host和port
            if ':' in server_ip:
                host, port = server_ip.split(':', 1)
            else:
                host, port = server_ip, ""
            self.results_tree.insert('', tk.END, values=(host, port, status, "-", error_msg))
        self.exec_status_var.set(f"执行状态: 成功 {success_count} / 失败 {fail_count}")

    def _execute_scripts_concurrent(self, servers):
        """并发执行脚本"""
        total_servers = len(servers)
        completed_count = 0
        results = [None] * total_servers  # 预分配结果数组，保持顺序

        # 重置取消事件
        self.cancel_event.clear()

        # 读取SQL脚本
        try:
            with open(self.script_file.get(), 'r', encoding='utf-8') as f:
                sql_content = f.read()
        except Exception as e:
            self.root.after(0, messagebox.showerror, "错误", f"读取脚本文件失败: {e}")
            self.root.after(0, self._set_execution_state, False)
            return

        # 解析SQL语句（处理delimiter）
        sql_statements = self._parse_sql_statements(sql_content)

        # 获取当前线程数设置
        max_workers = self.thread_count_var.get()

        try:
            with ThreadPoolExecutor(max_workers=max_workers) as executor:
                self.thread_pool = executor

                # 提交所有任务，保持索引映射
                future_to_index = {executor.submit(self._execute_single_script, host, port, sql_statements): i
                                 for i, (host, port) in enumerate(servers)}

                # 处理完成的任务
                for future in as_completed(future_to_index):
                    if self.cancel_event.is_set():
                        break

                    index = future_to_index[future]
                    host, port = servers[index]
                    completed_count += 1

                    try:
                        result = future.result()
                        results[index] = (host, port, result['status'], result['duration'], result['error'])
                    except Exception as e:
                        results[index] = (host, port, "执行失败", "-", str(e))

                    # 更新进度
                    progress = (completed_count / total_servers) * 100
                    self.root.after(0, self._update_progress, progress, f"已执行 {completed_count}/{total_servers}")

                # 按顺序更新UI
                self.root.after(0, self._update_results_in_order, results)

        except Exception as e:
            self.root.after(0, messagebox.showerror, "错误", f"执行过程中发生错误: {e}")
        finally:
            self.thread_pool = None
            if self.cancel_event.is_set():
                self.root.after(0, self._update_progress, 100, "执行已取消")
            else:
                self.root.after(0, self._update_progress, 100, "执行完成")
            self.root.after(0, self._set_execution_state, False)

    def _execute_single_script(self, host, port, sql_statements):
        """在单个服务器上执行脚本"""
        if self.cancel_event.is_set():
            return {'status': '已取消', 'duration': '-', 'error': '用户取消'}

        try:
            start_time = datetime.now()

            # 获取当前选择的数据库应用
            db_app = self.db_app.get()
            db_config = None

            # 从数据库配置中查找匹配的配置
            for config in self.database_configs:
                if config['db_app'] == db_app:
                    db_config = config
                    break

            if not db_config:
                return {'status': '执行失败', 'duration': '-', 'error': f'未找到数据库应用 "{db_app}" 的配置'}

            # print(db_config['db_user'])

            # 连接数据库
            connection = pymysql.connect(
                host=host,
                port=port,
                user=db_config['db_user'],
                password=db_config['db_password'],
                database=db_config['db_name'],
                connect_timeout=10
            )

            cursor = connection.cursor()

            # 执行SQL语句
            for statement in sql_statements:
                if self.cancel_event.is_set():
                    break
                if statement.strip():
                    cursor.execute(statement)

            if not self.cancel_event.is_set():
                connection.commit()

            cursor.close()
            connection.close()

            end_time = datetime.now()
            duration = (end_time - start_time).total_seconds()

            if self.cancel_event.is_set():
                return {'status': '已取消', 'duration': f"{duration:.2f}秒", 'error': '用户取消'}
            else:
                return {'status': '执行成功', 'duration': f"{duration:.2f}秒", 'error': ''}

        except Exception as e:
            return {'status': '执行失败', 'duration': '-', 'error': str(e)}

    def _parse_sql_statements(self, sql_content):
        """解析SQL语句，处理delimiter"""
        statements = []
        current_delimiter = ';'
        current_statement = ''

        lines = sql_content.split('\n')

        for line in lines:
            line = line.strip()

            # 跳过注释和空行
            if not line or line.startswith('--') or line.startswith('#'):
                continue

            # 处理delimiter命令
            if line.upper().startswith('DELIMITER'):
                parts = line.split()
                if len(parts) > 1:
                    current_delimiter = parts[1]
                continue

            # 添加到当前语句
            current_statement += line + '\n'

            # 检查是否到达语句结束
            if line.endswith(current_delimiter):
                # 移除delimiter并添加到语句列表
                statement = current_statement.rstrip('\n').rstrip(current_delimiter).strip()
                if statement:
                    statements.append(statement)
                current_statement = ''

        # 处理最后一个语句（如果没有以delimiter结尾）
        if current_statement.strip():
            statements.append(current_statement.strip())

        # 修正：去除所有空字符串
        statements = [s for s in statements if s.strip()]
        return statements

    def parse_servers(self):
        """解析服务器列表"""
        server_text = self.server_text.get(1.0, tk.END).strip()
        servers = []

        for line in server_text.split('\n'):
            line = line.strip()
            if line:
                if ':' in line:
                    host, port = line.split(':', 1)
                    try:
                        port = int(port)
                    except ValueError:
                        port = 3306
                else:
                    host = line
                    port = 3306
                servers.append((host.strip(), port))

        # 更新服务器数量显示
        self.server_count_var.set(f"服务器: {len(servers)}")
        return servers

    def _update_result_row(self, host, port, status, duration, error):
        """更新结果表格行"""
        self.results_tree.insert('', tk.END, values=(host, port, status, duration, error))

        # 统计执行状态
        success_count = 0
        fail_count = 0
        for item in self.results_tree.get_children():
            values = self.results_tree.item(item, 'values')
            if values and len(values) > 2:
                if "成功" in values[2]:
                    success_count += 1
                elif "失败" in values[2]:
                    fail_count += 1

        self.exec_status_var.set(f"执行状态: 成功 {success_count} / 失败 {fail_count}")


    def show_config_window(self):
        """显示配置窗口"""
        if hasattr(self, 'config_window') and self.config_window.winfo_exists():
            self.config_window.lift()
            return

        # 先计算居中位置
        self.root.update_idletasks()  # 确保主窗口尺寸已更新
        root_x = self.root.winfo_x()
        root_y = self.root.winfo_y()
        root_width = self.root.winfo_width()
        root_height = self.root.winfo_height()

        config_width = 800
        config_height = 550

        center_x = root_x + (root_width // 2) - (config_width // 2)
        center_y = root_y + (root_height // 2) - (config_height // 2)

        # 创建窗口时直接设置位置
        self.config_window = tk.Toplevel(self.root)
        self.config_window.geometry(f"{config_width}x{config_height}+{center_x}+{center_y}")
        self.config_window.title("系统配置")
        self.config_window.resizable(False, False)
        self.config_window.transient(self.root)

        # 使用新的resource_path函数来获取图标路径
        try:
            icon_path = resource_path('mysql.ico')
            self.config_window.iconbitmap(icon_path)
        except Exception as e:
            print(f"警告：无法为配置窗口设置图标: {e}")

        # 配置窗口UI
        self.create_config_ui()
        # 确保配置窗口中的密码框显示状态正确
        self._update_password_mask_display() 

    def create_config_ui(self):
        # 添加以下两行代码使配置窗口保持在主窗口前面
        self.config_window.transient(self.root)  # 设置为主窗口的子窗口
        self.config_window.grab_set()  # 获取焦点，阻止主窗口操作

        # 创建Notebook分组
        self.config_notebook = ttk.Notebook(self.config_window)
        self.config_notebook.pack(fill='both', expand=True, padx=10, pady=10)

        # 程序设置选项卡
        self.program_settings_frame = ttk.Frame(self.config_notebook, padding=10)
        self.config_notebook.add(self.program_settings_frame, text="程序设置")

        # 数据库配置选项卡
        self.db_config_frame = ttk.Frame(self.config_notebook, padding=10)
        self.config_notebook.add(self.db_config_frame, text="MySQL数据库配置")

        # 服务器源库配置选项卡
        self.server_db_config_frame = ttk.Frame(self.config_notebook, padding=10)
        self.config_notebook.add(self.server_db_config_frame, text="服务器列表源")

        # 设置网格权重
        self.program_settings_frame.columnconfigure(1, weight=1)
        self.db_config_frame.columnconfigure(0, weight=1)
        self.db_config_frame.rowconfigure(0, weight=1)
        self.server_db_config_frame.columnconfigure(0, weight=1)
        self.server_db_config_frame.rowconfigure(0, weight=1)

        # 创建程序设置界面
        self.create_program_settings_ui()

        # 创建数据库配置界面
        self.create_db_config_ui()

        # 创建服务器源库配置界面
        self.create_server_db_config_ui()

        # 保存按钮
        save_frame = ttk.Frame(self.config_window)
        save_frame.pack(fill='x', padx=10, pady=10)
        ttk.Button(save_frame, text="保存配置", command=self.save_config).pack(pady=1)

    def create_program_settings_ui(self):
        """创建程序设置界面"""
        # 并发数设置
        ttk.Label(self.program_settings_frame, text="最大并发数:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.config_max_threads = ttk.Spinbox(self.program_settings_frame, from_=1, to=50, width=10)
        self.config_max_threads.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
        self.config_max_threads.set(self.program_settings.get('max_threads', 10))

        # 新增：默认合并结果开关 (独立于主界面开关)
        ttk.Label(self.program_settings_frame, text="默认合并结果:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)
        self.config_merge_result_var = tk.BooleanVar(value=self.program_settings.get("merge_result_enabled", False))
        self.merge_result_checkbox = ttk.Checkbutton(
            self.program_settings_frame,
            variable=self.config_merge_result_var,
            text="勾选后执行SQL时默认合并所有服务器结果"
        )
        self.merge_result_checkbox.grid(row=0, column=3, sticky=tk.W, padx=5, pady=5)

        # 新增：密码显示*设置
        ttk.Label(self.program_settings_frame, text="密码显示*:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        self.password_mask_checkbox = ttk.Checkbutton(
            self.program_settings_frame,
            variable=self.password_mask_enabled,
            text="勾选后密码显示为*，不勾选显示明文",
            command=self._update_password_mask_display # Add command to trigger update
        )
        self.password_mask_checkbox.grid(row=1, column=1, sticky=tk.W, padx=5, pady=5)

        # 新增：日志开关
        ttk.Label(self.program_settings_frame, text="启用日志:").grid(row=1, column=2, sticky=tk.W, padx=5, pady=5)
        self.config_enable_logging = tk.BooleanVar(value=self.program_settings.get("enable_logging", False))
        self.enable_logging_checkbox = ttk.Checkbutton(
            self.program_settings_frame,
            variable=self.config_enable_logging,
            text="勾选后启用日志记录到mysql_tool.log"
        )
        self.enable_logging_checkbox.grid(row=1, column=3, sticky=tk.W, padx=5, pady=5)

        
        # Default Save Path
        ttk.Label(self.program_settings_frame, text="默认表格保存路径:").grid(row=2, column=0, sticky=tk.W, pady=(5, 2))
        save_path_frame = ttk.Frame(self.program_settings_frame)
        save_path_frame.grid(row=2, column=1, columnspan=3, sticky=tk.EW, padx=5, pady=5)
        save_path_frame.columnconfigure(0, weight=1)

        self.config_save_path_var = tk.StringVar(value=self.program_settings.get('default_save_path', ''))
        self.config_save_path_entry = ttk.Entry(save_path_frame, textvariable=self.config_save_path_var, width=50)
        self.config_save_path_entry.grid(row=0, column=0, sticky=(tk.W, tk.E))
        ttk.Button(save_path_frame, text="浏览", command=self._browse_config_save_path).grid(row=0, column=1, sticky=tk.W, padx=(5, 0))

        # 默认脚本路径
        ttk.Label(self.program_settings_frame, text="默认脚本路径:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
        script_frame = ttk.Frame(self.program_settings_frame)
        script_frame.grid(row=3, column=1,columnspan=3,  sticky=tk.EW, padx=5, pady=5)
        script_frame.columnconfigure(0, weight=1)
        script_frame.rowconfigure(1, weight=1) # Allow scrolledtext to expand

        self.config_script_path = scrolledtext.ScrolledText(script_frame, width=50, height=7, wrap=tk.WORD) # Changed to ScrolledText
        self.config_script_path.grid(row=0, column=0, sticky=(tk.W, tk.E))
        self.config_script_path.insert(tk.END, self.program_settings.get('default_script', 'script.sql'))

        ttk.Button(script_frame, text="浏览", command=self.browse_default_script).grid(row=0, column=1, sticky=tk.W, pady=(0,3)) # Adjusted button grid

        # 默认SQL语句
        ttk.Label(self.program_settings_frame, text="默认SQL语句:").grid(row=4, column=0, sticky=tk.NW, padx=5, pady=5)
        sql_frame = ttk.Frame(self.program_settings_frame)
        sql_frame.grid(row=4, column=1, columnspan=3, sticky=tk.EW, padx=5, pady=5)
        sql_frame.columnconfigure(0, weight=1)

        self.config_default_sql = scrolledtext.ScrolledText(sql_frame, width=50, height=7, wrap=tk.WORD)
        self.config_default_sql.grid(row=0, column=0, sticky=tk.EW)
        self.config_default_sql.insert(tk.END, self.program_settings.get('default_sql', 'SELECT * FROM table_name;'))

        # 新增：默认服务器列表
        ttk.Label(self.program_settings_frame, text="默认服务器列表:").grid(row=5, column=0, sticky=tk.NW, padx=5,
                                                                            pady=5)
        servers_frame = ttk.Frame(self.program_settings_frame)
        servers_frame.grid(row=5, column=1, columnspan=3, sticky=tk.EW, padx=5, pady=5)
        servers_frame.columnconfigure(0, weight=1)

        self.config_default_servers = scrolledtext.ScrolledText(servers_frame, width=50, height=7, wrap=tk.WORD)
        self.config_default_servers.grid(row=0, column=0, sticky=tk.EW)
        # 加载现有默认服务器列表（从配置中读取）
        default_servers = self.program_settings.get('default_servers',
                                                    '192.168.1.100:3306\n192.168.1.101\n192.168.1.102:3307')
        self.config_default_servers.insert(tk.END, default_servers)



    def create_db_config_ui(self):
        """创建数据库配置界面"""
        # 创建表格框架
        table_frame = ttk.Frame(self.db_config_frame)
        table_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

        # 创建数据库配置表格
        columns = ('db_app', 'db_name', 'db_user', 'db_password')
        self.db_config_tree = ttk.Treeview(table_frame, columns=columns, show='headings', height=5)

        # 设置列标题
        self.db_config_tree.heading('db_app', text='数据库应用')
        self.db_config_tree.heading('db_name', text='数据库名称')
        self.db_config_tree.heading('db_user', text='用户名')
        self.db_config_tree.heading('db_password', text='密码')

        # 设置列宽
        self.db_config_tree.column('db_app', width=100)
        self.db_config_tree.column('db_name', width=150)
        self.db_config_tree.column('db_user', width=100)
        self.db_config_tree.column('db_password', width=100)

        # 添加垂直滚动条
        yscrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.db_config_tree.yview)
        self.db_config_tree.configure(yscrollcommand=yscrollbar.set)

        # 添加水平滚动条
        xscrollbar = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=self.db_config_tree.xview)
        self.db_config_tree.configure(xscrollcommand=xscrollbar.set)

        # 布局表格和滚动条
        self.db_config_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        yscrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        xscrollbar.grid(row=1, column=0, sticky=(tk.W, tk.E))

        # 确保表格框架可以扩展
        table_frame.columnconfigure(0, weight=1)
        table_frame.rowconfigure(0, weight=1)

        # 绑定双击事件
        self.db_config_tree.bind('<Double-1>', self.on_db_config_double_click)

        # 添加按钮框架
        button_frame = ttk.Frame(self.db_config_frame)
        button_frame.grid(row=1, column=0, sticky=tk.EW, pady=(5, 0))

        # 添加按钮
        ttk.Button(button_frame, text="添加", command=self.add_db_config).grid(row=0, column=0, padx=5)
        ttk.Button(button_frame, text="修改", command=self.update_db_config).grid(row=0, column=1, padx=5)
        ttk.Button(button_frame, text="删除", command=self.delete_db_config).grid(row=0, column=2, padx=5)
        ttk.Button(button_frame, text="向上", command=self.move_db_config_up).grid(row=0, column=3, padx=5)
        ttk.Button(button_frame, text="向下", command=self.move_db_config_down).grid(row=0, column=4, padx=5)

        # 编辑框架 - 固定显示
        self.edit_frame = ttk.LabelFrame(self.db_config_frame, text="数据库配置编辑", padding=2)
        self.edit_frame.grid(row=2, column=0, sticky=tk.EW, pady=(5, 0))
        self.edit_frame.columnconfigure(1, weight=1)
        self.edit_frame.columnconfigure(3, weight=1)

        # 数据库应用
        ttk.Label(self.edit_frame, text="数据库应用:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.edit_db_app = ttk.Entry(self.edit_frame, width=20)
        self.edit_db_app.grid(row=0, column=1, sticky=tk.EW, padx=5, pady=5)

        # 数据库名称
        ttk.Label(self.edit_frame, text="数据库名称:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)
        self.edit_db_name = ttk.Entry(self.edit_frame, width=20)
        self.edit_db_name.grid(row=0, column=3, sticky=tk.EW, padx=5, pady=5)

        # 用户名
        ttk.Label(self.edit_frame, text="用户名:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        self.edit_db_user = ttk.Entry(self.edit_frame, width=20)
        self.edit_db_user.grid(row=1, column=1, sticky=tk.EW, padx=5, pady=5)

        # 密码
        ttk.Label(self.edit_frame, text="密码:").grid(row=1, column=2, sticky=tk.W, padx=5, pady=5)
        self.edit_db_password = ttk.Entry(
            self.edit_frame,
            width=20,
            show="*" if self.password_mask_enabled.get() else "" # Add dynamic control here
        )
        self.edit_db_password.grid(row=1, column=3, sticky=tk.EW, padx=5, pady=5)

        # 设置控件为可编辑状态
        self.edit_db_app.config(state='disabled')
        self.edit_db_name.config(state='disabled')
        self.edit_db_user.config(state='disabled')
        self.edit_db_password.config(state='disabled')
        # self.edit_confirm_btn.config(state='disabled')

        # 确认按钮
        button_frame = ttk.Frame(self.edit_frame)
        button_frame.grid(row=2, column=0, columnspan=4, pady=5)

        self.edit_confirm_btn = ttk.Button(button_frame, text="确认", command=self.confirm_db_config_edit)
        self.edit_confirm_btn.grid(row=0, column=0, padx=5)

        ttk.Button(button_frame, text="取消", command=self.cancel_db_config_edit).grid(row=0, column=1, padx=5)

        # 加载现有配置到表格
        self.load_db_config_to_tree()

        # 初始化编辑模式
        self.edit_mode = "add"
        self.edit_item_id = None

    def create_server_db_config_ui(self):
        """创建服务器源库配置界面"""
        # 创建表格框架
        table_frame = ttk.Frame(self.server_db_config_frame)
        table_frame.grid(row=0, column=0, sticky=(tk.W, tk.E))

        # 创建服务器源库配置表格
        columns = ('db_app', 'db_type', 'db_ip','db_name', 'username', 'password', 'list_sql')
        self.server_db_config_tree = ttk.Treeview(table_frame, columns=columns, show='headings', height=8)

        # 设置列标题
        self.server_db_config_tree.heading('db_app', text='数据库应用')
        self.server_db_config_tree.heading('db_type', text='数据库类型')
        self.server_db_config_tree.heading('db_ip', text='数据库IP')
        self.server_db_config_tree.heading('db_name', text='数据库名称')
        self.server_db_config_tree.heading('username', text='用户名')
        self.server_db_config_tree.heading('password', text='密码')
        self.server_db_config_tree.heading('list_sql', text='获取列表SQL')

        # 设置列宽
        self.server_db_config_tree.column('db_app', width=100)
        self.server_db_config_tree.column('db_type', width=100)
        self.server_db_config_tree.column('db_ip', width=120)
        self.server_db_config_tree.column('db_name', width=150)
        self.server_db_config_tree.column('username', width=100)
        self.server_db_config_tree.column('password', width=100)
        self.server_db_config_tree.column('list_sql', width=200)

        # 添加垂直滚动条
        yscrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.server_db_config_tree.yview)
        self.server_db_config_tree.configure(yscrollcommand=yscrollbar.set)

        # 添加水平滚动条
        xscrollbar = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=self.server_db_config_tree.xview)
        self.server_db_config_tree.configure(xscrollcommand=xscrollbar.set)

        # 布局表格和滚动条
        self.server_db_config_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        yscrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        xscrollbar.grid(row=1, column=0, sticky=(tk.W, tk.E))

        # 确保表格框架可以扩展
        table_frame.columnconfigure(0, weight=1)
        table_frame.rowconfigure(0, weight=1)

        # 绑定双击事件
        self.server_db_config_tree.bind('<Double-1>', self.on_server_db_config_double_click)

        # 添加按钮框架
        button_frame = ttk.Frame(self.server_db_config_frame)
        button_frame.grid(row=1, column=0, sticky=tk.EW, pady=(5, 0))

        # 添加按钮
        ttk.Button(button_frame, text="添加", command=self.add_server_db_config).grid(row=0, column=0, padx=5)
        ttk.Button(button_frame, text="修改", command=self.update_server_db_config).grid(row=0, column=1, padx=5)
        ttk.Button(button_frame, text="删除", command=self.delete_server_db_config).grid(row=0, column=2, padx=5)
        ttk.Button(button_frame, text="向上", command=self.move_server_db_config_up).grid(row=0, column=3, padx=5)
        ttk.Button(button_frame, text="向下", command=self.move_server_db_config_down).grid(row=0, column=4, padx=5)

        # 编辑框架 - 固定显示
        self.server_edit_frame = ttk.LabelFrame(self.server_db_config_frame, text="服务器源库配置编辑", padding=2)
        self.server_edit_frame.grid(row=2, column=0, sticky=tk.EW, pady=(5, 0))
        self.server_edit_frame.columnconfigure(1, weight=1)
        self.server_edit_frame.columnconfigure(3, weight=1)

        # 数据库应用
        ttk.Label(self.server_edit_frame, text="数据库应用:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.server_edit_db_app = ttk.Entry(self.server_edit_frame, width=20)
        self.server_edit_db_app.grid(row=0, column=1, sticky=tk.EW, padx=5, pady=5)

        # 数据库类型
        ttk.Label(self.server_edit_frame, text="数据库类型:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)
        self.server_edit_db_type = tk.StringVar()
        self.server_edit_db_type_combo = ttk.Combobox(self.server_edit_frame, textvariable=self.server_edit_db_type, width=20, state="readonly")
        self.server_edit_db_type_combo.grid(row=0, column=3, sticky=tk.EW, padx=5, pady=5)
        self.server_edit_db_type_combo['values'] = ('MySQL', 'SQL Server')

        # 数据库IP
        ttk.Label(self.server_edit_frame, text="数据库IP:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        self.server_edit_db_ip = ttk.Entry(self.server_edit_frame, width=20)
        self.server_edit_db_ip.grid(row=1, column=1, sticky=tk.EW, padx=5, pady=5)

        # 数据库名称
        ttk.Label(self.server_edit_frame, text="数据库名称:").grid(row=1, column=2, sticky=tk.W, padx=5, pady=5)
        self.server_edit_db_name = ttk.Entry(self.server_edit_frame, width=20)
        self.server_edit_db_name.grid(row=1, column=3, sticky=tk.EW, padx=5, pady=5)

        # 用户名
        ttk.Label(self.server_edit_frame, text="用户名:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
        self.server_edit_username = ttk.Entry(self.server_edit_frame, width=20)
        self.server_edit_username.grid(row=2, column=1, sticky=tk.EW, padx=5, pady=5)

        # 密码（修改show参数为动态控制）
        ttk.Label(self.server_edit_frame, text="密码:").grid(row=2, column=2, sticky=tk.W, padx=5, pady=5)
        self.server_edit_password = ttk.Entry(
            self.server_edit_frame,
            width=20,
            show="*" if self.password_mask_enabled.get() else ""  # 动态控制
        )
        self.server_edit_password.grid(row=2, column=3, sticky=tk.EW, padx=5, pady=5)

        # 获取列表SQL
        ttk.Label(self.server_edit_frame, text="获取列表SQL:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
        self.server_edit_list_sql = ttk.Entry(self.server_edit_frame, width=50)
        self.server_edit_list_sql.grid(row=3, column=1, columnspan=3, sticky=tk.EW, padx=5, pady=5)

        # 设置控件为可编辑状态
        self.server_edit_db_app.config(state='disabled')
        self.server_edit_db_type_combo.config(state='disabled')
        self.server_edit_db_ip.config(state='disabled')
        self.server_edit_db_name.config(state='disabled')
        self.server_edit_username.config(state='disabled')
        self.server_edit_password.config(state='disabled')
        self.server_edit_list_sql.config(state='disabled')

        # 确认按钮
        button_frame = ttk.Frame(self.server_edit_frame)
        button_frame.grid(row=4, column=0, columnspan=4, pady=5)

        self.server_edit_confirm_btn = ttk.Button(button_frame, text="确认", command=self.confirm_server_db_config_edit)
        self.server_edit_confirm_btn.grid(row=0, column=0, padx=5)
        self.server_edit_confirm_btn.config(state='disabled')

        ttk.Button(button_frame, text="取消", command=self.cancel_server_db_config_edit).grid(row=0, column=1, padx=5)

        # 加载现有配置到表格
        self.load_server_db_config_to_tree()

        # 初始化编辑模式
        self.server_edit_mode = "add"
        self.server_edit_item_id = None

    def load_server_db_config_to_tree(self):
        """加载服务器源库配置到表格"""
        # 清空表格
        for item in self.server_db_config_tree.get_children():
            self.server_db_config_tree.delete(item)

        # 加载配置
        for config in self.server_database_configs:
            # 根据密码掩码开关决定显示明文或星号
            if self.password_mask_enabled.get():
                masked_password = '*' * len(config.get('password', ''))
            else:
                masked_password = config.get('password', '')  # 明文显示
            self.server_db_config_tree.insert('', 'end', values=(
                config.get('db_app', ''),
                config.get('db_type', ''),
                config.get('db_ip', ''),
                config.get('db_name', ''),
                config.get('username', ''),
                masked_password,
                config.get('list_sql', '')
            ))

    def on_server_db_config_double_click(self, event):
        """双击服务器源库配置表格行事件"""
        # 获取选中的项目
        selected_item = self.server_db_config_tree.selection()
        if not selected_item:
            return

        # 获取项目ID
        item_id = selected_item[0]

        # 获取项目值
        values = self.server_db_config_tree.item(item_id, 'values')

        # 设置控件为可编辑状态
        self.server_edit_db_app.config(state='normal')
        self.server_edit_db_type_combo.config(state='normal')
        self.server_edit_db_ip.config(state='normal')
        self.server_edit_db_name.config(state='normal')
        self.server_edit_username.config(state='normal')
        self.server_edit_password.config(state='normal')
        self.server_edit_list_sql.config(state='normal')
        self.server_edit_confirm_btn.config(state='normal')

        # 设置编辑模式
        self.server_edit_mode = "update"
        self.server_edit_item_id = item_id

        # 查找对应的配置项
        for config in self.server_database_configs:
            if (config.get('db_app') == values[0] and
                config.get('db_type') == values[1] and
                config.get('db_ip') == values[2] and
                config.get('db_name') == values[3] and
                config.get('username') == values[4]):

                # 填充编辑框
                self.server_edit_db_app.delete(0, tk.END)
                self.server_edit_db_app.insert(0, config.get('db_app', ''))

                # 填充数据库类型下拉框
                self.server_edit_db_type.set(config.get('db_type', 'MySQL'))

                self.server_edit_db_ip.delete(0, tk.END)
                self.server_edit_db_ip.insert(0, config.get('db_ip', ''))

                self.server_edit_db_name.delete(0, tk.END)
                self.server_edit_db_name.insert(0, config.get('db_name', ''))

                self.server_edit_username.delete(0, tk.END)
                self.server_edit_username.insert(0, config.get('username', ''))

                self.server_edit_password.delete(0, tk.END)
                self.server_edit_password.insert(0, config.get('password', ''))

                self.server_edit_list_sql.delete(0, tk.END)
                self.server_edit_list_sql.insert(0, config.get('list_sql', 'SELECT DISTINCT(stationserverip) FROM ZX_tollStation'))

                break

    def add_server_db_config(self):
        """添加服务器源库配置"""
        # 设置编辑模式
        self.server_edit_mode = "add"
        self.server_edit_item_id = None

        # 清空编辑框
        self.server_edit_db_app.delete(0, tk.END)
        self.server_edit_db_type.set('MySQL')  # 设置默认值
        self.server_edit_db_ip.delete(0, tk.END)
        self.server_edit_db_name.delete(0, tk.END)
        self.server_edit_username.delete(0, tk.END)
        self.server_edit_password.delete(0, tk.END)
        self.server_edit_list_sql.delete(0, tk.END)
        self.server_edit_list_sql.insert(0, 'SELECT DISTINCT(stationserverip) FROM ZX_tollStation')

        # 设置控件为可编辑状态
        self.server_edit_db_app.config(state='normal')
        self.server_edit_db_type_combo.config(state='normal')
        self.server_edit_db_ip.config(state='normal')
        self.server_edit_db_name.config(state='normal')
        self.server_edit_username.config(state='normal')
        self.server_edit_password.config(state='normal')
        self.server_edit_list_sql.config(state='normal')
        self.server_edit_confirm_btn.config(state='normal')

    def update_server_db_config(self):
        """更新服务器源库配置"""
        # 获取选中的项目
        selected_item = self.server_db_config_tree.selection()
        if not selected_item:
            messagebox.showwarning("警告", "请先选择要更新的配置项")
            return

        # 触发双击事件
        self.on_server_db_config_double_click(None)

    def delete_server_db_config(self):
        """删除服务器源库配置"""
        # 获取选中的项目
        selected_item = self.server_db_config_tree.selection()
        if not selected_item:
            messagebox.showwarning("警告", "请先选择要删除的配置项")
            return

        # 获取项目ID
        item_id = selected_item[0]

        # 获取项目值
        values = self.server_db_config_tree.item(item_id, 'values')

        # 确认删除
        if not messagebox.askyesno("确认", f"确定要删除数据库应用 '{values[0]}' 的配置吗？"):
            return

        # 查找并删除对应的配置项
        for i, config in enumerate(self.server_database_configs):
            if (config.get('db_app') == values[0] and
                config.get('db_type') == values[1] and
                config.get('db_ip') == values[2] and
                config.get('db_name') == values[3] and
                config.get('username') == values[4]):

                # 删除配置项
                self.server_database_configs.pop(i)
                self.save_json_config() # Save changes immediately after deletion
                break

        # 从表格中删除
        self.server_db_config_tree.delete(item_id)

        # 更新服务器源库应用下拉框
        self.server_db_types = [config.get('db_app') for config in self.server_database_configs]
        if self.server_db_combo['values'] != self.server_db_types:
            self.server_db_combo['values'] = self.server_db_types
            if self.server_db_types:
                self.server_db_combo.current(0)
                self.on_server_db_changed(None)

    def confirm_server_db_config_edit(self):
        """确认服务器源库配置编辑"""
        # 获取编辑框的值
        db_app = self.server_edit_db_app.get().strip()
        db_type = self.server_edit_db_type.get().strip()
        db_ip = self.server_edit_db_ip.get().strip()
        db_name = self.server_edit_db_name.get().strip()
        username = self.server_edit_username.get().strip()
        password = self.server_edit_password.get()
        list_sql = self.server_edit_list_sql.get().strip()

        # 验证输入
        if not db_app or not db_name or not username:
            messagebox.showwarning("警告", "数据库应用、数据库名称和用户名不能为空")
            return

        # 创建配置项
        config_item = {
            'db_app': db_app,
            'db_type': db_type,
            'db_ip': db_ip,
            'db_name': db_name,
            'username': username,
            'password': password,
            'list_sql': list_sql
        }

        if self.server_edit_mode == "add":
            # 添加配置项
            self.server_database_configs.append(config_item)

            # 添加到表格
            if self.password_mask_enabled.get():
                masked_password = '*' * len(password)
            else:
                masked_password = password # 明文显示
            self.server_db_config_tree.insert('', 'end', values=(db_app, db_type, db_ip, db_name, username, masked_password, list_sql))

            # 更新服务器源库应用下拉框
            if db_app not in self.server_db_types:
                self.server_db_types.append(db_app)
                self.server_db_combo['values'] = self.server_db_types

        elif self.server_edit_mode == "update":
            # 查找并更新对应的配置项
            values = self.server_db_config_tree.item(self.server_edit_item_id, 'values')
            for i, config in enumerate(self.server_database_configs):
                if (config.get('db_app') == values[0] and
                    config.get('db_type') == values[1] and
                    config.get('db_ip') == values[2] and
                    config.get('db_name') == values[3] and
                    config.get('username') == values[4]):

                    # 更新配置项
                    self.server_database_configs[i] = config_item
                    break

            # 更新表格
            if self.password_mask_enabled.get():
                masked_password = '*' * len(password)
            else:
                masked_password = password # 明文显示
            self.server_db_config_tree.item(self.server_edit_item_id, values=(db_app, db_type, db_ip, db_name, username, masked_password, list_sql))

            # 更新服务器源库应用下拉框
            self.server_db_types = list(set(config.get('db_app') for config in self.server_database_configs))
            self.server_db_combo['values'] = self.server_db_types

        # 清空所有编辑框
        self.server_edit_db_app.delete(0, tk.END)
        self.server_edit_db_type.set('')
        self.server_edit_db_ip.delete(0, tk.END)
        self.server_edit_db_name.delete(0, tk.END)
        self.server_edit_username.delete(0, tk.END)
        self.server_edit_password.delete(0, tk.END)
        self.server_edit_list_sql.delete(0, tk.END)

        # 设置控件为非编辑状态
        self.server_edit_db_app.config(state='disabled')
        self.server_edit_db_type_combo.config(state='disabled')
        self.server_edit_db_ip.config(state='disabled')
        self.server_edit_db_name.config(state='disabled')
        self.server_edit_username.config(state='disabled')
        self.server_edit_password.config(state='disabled')
        self.server_edit_list_sql.config(state='disabled')
        self.server_edit_confirm_btn.config(state='disabled')

        # 重置编辑模式
        self.server_edit_mode = "add"
        self.server_edit_item_id = None

    def cancel_server_db_config_edit(self):
        """取消服务器源库配置编辑"""
        # 清空所有编辑框
        self.server_edit_db_app.delete(0, tk.END)
        self.server_edit_db_type.set('')
        self.server_edit_db_ip.delete(0, tk.END)
        self.server_edit_db_name.delete(0, tk.END)
        self.server_edit_username.delete(0, tk.END)
        self.server_edit_password.delete(0, tk.END)
        self.server_edit_list_sql.delete(0, tk.END)

        # 设置控件为非编辑状态
        self.server_edit_db_app.config(state='disabled')
        self.server_edit_db_type_combo.config(state='disabled')
        self.server_edit_db_ip.config(state='disabled')
        self.server_edit_db_name.config(state='disabled')
        self.server_edit_username.config(state='disabled')
        self.server_edit_password.config(state='disabled')
        self.server_edit_list_sql.config(state='disabled')
        self.server_edit_confirm_btn.config(state='disabled')

        # 重置编辑模式
        self.server_edit_mode = "add"
        self.server_edit_item_id = None

    def save_config(self):
        """保存配置"""
        # 更新程序设置（新增密码掩码、合并结果）
        self.program_settings['max_threads'] = int(self.config_max_threads.get())
        self.program_settings['default_script'] = self.config_script_path.get(1.0, tk.END).strip() # Get full text from ScrolledText
        self.program_settings['default_sql'] = self.config_default_sql.get(1.0, tk.END).strip()
        self.program_settings['default_servers'] = self.config_default_servers.get(1.0, tk.END).strip()
        self.program_settings['password_mask_enabled'] = self.password_mask_enabled.get()
        self.program_settings['merge_result_enabled'] = self.config_merge_result_var.get()  # 从config_merge_result_var获取值
        self.program_settings['enable_logging'] = self.config_enable_logging.get()
        self.program_settings['default_save_path'] = self.config_save_path_var.get()

        # 更新最大线程数
        self.max_threads = self.program_settings['max_threads']
        self.thread_count_var.set(self.max_threads)

        # 保存到JSON文件
        self.save_json_config()
        self._setup_logging()

        # 关闭配置窗口
        self.config_window.destroy()
        messagebox.showinfo("提示", "配置已保存")

    def browse_default_script(self):
        """浏览默认脚本文件（支持多选）"""
        filenames = filedialog.askopenfilenames(filetypes=[("SQL文件", "*.sql"), ("所有文件", "*.*")])
        if filenames:
            self.config_script_path.config(state="normal")
            self.config_script_path.delete(1.0, tk.END)
            for f in filenames:
                self.config_script_path.insert(tk.END, f + "\n")
            self.config_script_path.config(state="disabled")
            self.logger.info(f"设置默认脚本路径: {filenames}")

    def _browse_config_save_path(self):
        """Opens a dialog to choose a file for the default save path in the config window."""
        filepath = filedialog.asksaveasfilename(
            title="指定默认Excel保存路径",
            initialdir=self.config_save_path_var.get() or os.getcwd(), # Start from current path or CWD
            defaultextension=".xlsx",
            filetypes=[("Excel 文件", "*.xlsx"), ("所有文件", "*.*")]
        )
        if filepath:
            self.config_save_path_var.set(filepath)

    def load_db_config_to_tree(self):
        """加载数据库配置到表格"""
        # 清空表格
        for item in self.db_config_tree.get_children():
            self.db_config_tree.delete(item)

        # 加载配置
        for config in self.database_configs:
            # 显示密码时用星号替代
            if self.password_mask_enabled.get():
                masked_password = '*' * len(config.get('db_password', ''))
            else:
                masked_password = config.get('db_password', '') # 明文显示
            self.db_config_tree.insert('', 'end', values=(
                config.get('db_app', ''),
                config.get('db_name', ''),
                config.get('db_user', ''),
                masked_password
            ))

    def on_db_config_double_click(self, event):
        """双击数据库配置表格行事件"""
        # 获取选中的项目
        selected_item = self.db_config_tree.selection()
        if not selected_item:
            return

        # 获取项目ID
        item_id = selected_item[0]

        # 获取项目值
        values = self.db_config_tree.item(item_id, 'values')

        # 设置控件为可编辑状态
        self.edit_db_app.config(state='normal')
        self.edit_db_name.config(state='normal')
        self.edit_db_user.config(state='normal')
        self.edit_db_password.config(state='normal')
        self.edit_confirm_btn.config(state='normal')

        # 设置编辑模式
        self.edit_mode = "update"
        self.edit_item_id = item_id

        # 查找对应的配置项
        for config in self.database_configs:
            if (config.get('db_app') == values[0] and
                config.get('db_name') == values[1] and
                config.get('db_user') == values[2]):

                # 填充编辑框
                self.edit_db_app.delete(0, tk.END)
                self.edit_db_app.insert(0, config.get('db_app', ''))

                self.edit_db_name.delete(0, tk.END)
                self.edit_db_name.insert(0, config.get('db_name', ''))

                self.edit_db_user.delete(0, tk.END)
                self.edit_db_user.insert(0, config.get('db_user', ''))

                self.edit_db_password.delete(0, tk.END)
                self.edit_db_password.insert(0, config.get('db_password', ''))

                break



        # 显示编辑框架
        # self.edit_confirm_btn.configure(text="更新")

    def add_db_config(self):
        """添加数据库配置"""
        # 设置编辑模式
        self.edit_mode = "add"
        self.edit_item_id = None

        # 清空编辑框
        self.edit_db_app.delete(0, tk.END)
        self.edit_db_name.delete(0, tk.END)
        self.edit_db_user.delete(0, tk.END)
        self.edit_db_password.delete(0, tk.END)

        # 设置控件为可编辑状态
        self.edit_db_app.config(state='normal')
        self.edit_db_name.config(state='normal')
        self.edit_db_user.config(state='normal')
        self.edit_db_password.config(state='normal')
        self.edit_confirm_btn.config(state='normal')

        # 显示编辑框架
        # self.edit_confirm_btn.configure(text="添加")

    def update_db_config(self):
        """更新数据库配置"""
        # 获取选中的项目
        selected_item = self.db_config_tree.selection()
        if not selected_item:
            messagebox.showwarning("警告", "请先选择要更新的配置项")
            return

        # 触发双击事件
        self.on_db_config_double_click(None)

    def delete_db_config(self):
        """删除数据库配置"""
        # 获取选中的项目
        selected_item = self.db_config_tree.selection()
        if not selected_item:
            messagebox.showwarning("警告", "请先选择要删除的配置项")
            return

        # 获取项目ID
        item_id = selected_item[0]

        # 获取项目值
        values = self.db_config_tree.item(item_id, 'values')

        # 确认删除
        if not messagebox.askyesno("确认", f"确定要删除数据库应用 '{values[0]}' 的配置吗？"):
            return

        # 查找并删除对应的配置项
        for i, config in enumerate(self.database_configs):
            if (config.get('db_app') == values[0] and
                config.get('db_name') == values[1] and
                config.get('db_user') == values[2]): # Corrected condition: values[2] should be db_user

                # 删除配置项
                self.database_configs.pop(i)
                self.save_json_config() # Save changes immediately after deletion
                break

        # 从表格中删除
        self.db_config_tree.delete(item_id)

        # 更新数据库应用下拉框
        self.db_types = [config.get('db_app') for config in self.database_configs]
        if self.db_type_combo['values'] != self.db_types:
            self.db_type_combo['values'] = self.db_types
            if self.db_types:
                self.db_type_combo.current(0)
                self.on_db_type_changed(None)

    def confirm_db_config_edit(self):
        """确认数据库配置编辑"""
        # 获取编辑框的值
        db_app = self.edit_db_app.get().strip()
        db_name = self.edit_db_name.get().strip()
        db_user = self.edit_db_user.get().strip()
        db_password = self.edit_db_password.get()

        # 验证输入
        if not db_app or not db_name or not db_user:
            messagebox.showwarning("警告", "数据库应用、数据库名称和用户名不能为空")
            return

        # 创建配置项
        config_item = {
            'db_app': db_app,
            'db_name': db_name,
            'db_user': db_user,
            'db_password': db_password
        }

        if self.edit_mode == "add":
            # 添加配置项
            self.database_configs.append(config_item)

            # 添加到表格
            if self.password_mask_enabled.get():
                masked_password = '*' * len(db_password)
            else:
                masked_password = db_password # 明文显示
            self.db_config_tree.insert('', 'end', values=(db_app, db_name, db_user, masked_password))

            # 更新数据库应用下拉框
            if db_app not in self.db_types:
                self.db_types.append(db_app)
                self.db_type_combo['values'] = self.db_types

        elif self.edit_mode == "update":
            # 查找并更新对应的配置项
            values = self.db_config_tree.item(self.edit_item_id, 'values')
            for i, config in enumerate(self.database_configs):
                if (config.get('db_app') == values[0] and
                    config.get('db_name') == values[1] and
                    config.get('db_user') == values[2]):

                    # 更新配置项
                    self.database_configs[i] = config_item
                    break

            # 更新表格
            if self.password_mask_enabled.get():
                masked_password = '*' * len(db_password)
            else:
                masked_password = db_password # 明文显示
            self.db_config_tree.item(self.edit_item_id, values=(db_app, db_name, db_user, masked_password))

            # 更新数据库应用下拉框
            self.db_types = list(set(config.get('db_app') for config in self.database_configs))
            self.db_type_combo['values'] = self.db_types

        # 清空所有编辑框
        self.edit_db_app.delete(0, tk.END)
        self.edit_db_name.delete(0, tk.END)
        self.edit_db_user.delete(0, tk.END)
        self.edit_db_password.delete(0, tk.END)

        # 设置控件为非编辑状态
        self.edit_db_app.config(state='disabled')
        self.edit_db_name.config(state='disabled')
        self.edit_db_user.config(state='disabled')
        self.edit_db_password.config(state='disabled')
        self.edit_confirm_btn.config(state='disabled')


        # 重置编辑模式
        self.edit_mode = "add"
        self.edit_item_id = None

    def cancel_db_config_edit(self):
        """取消数据库配置编辑"""
        """取消数据库配置编辑"""
        # 清空所有编辑框
        self.edit_db_app.delete(0, tk.END)
        self.edit_db_name.delete(0, tk.END)
        self.edit_db_user.delete(0, tk.END)
        self.edit_db_password.delete(0, tk.END)

        # 设置控件为非编辑状态
        self.edit_db_app.config(state='disabled')
        self.edit_db_name.config(state='disabled')
        self.edit_db_user.config(state='disabled')
        self.edit_db_password.config(state='disabled')
        self.edit_confirm_btn.config(state='disabled')

        # 重置编辑模式
        self.edit_mode = "add"
        self.edit_item_id = None

    def _on_config_window_close(self):
        """配置窗口关闭事件"""
        if hasattr(self, 'config_window'):
            self.config_window.grab_release()  # 释放焦点
            self.config_window.destroy()
            del self.config_window
        self.config_window.destroy()

    def _update_results_in_order(self, results):
        """按顺序更新结果到表格"""
        for result in results:
            if result is not None:
                host, port, status, duration, error = result
                self.results_tree.insert('', tk.END, values=(host, port, status, duration, error))

        # 统计执行状态
        success_count = 0
        fail_count = 0
        for item in self.results_tree.get_children():
            values = self.results_tree.item(item, 'values')
            if values and len(values) > 2:
                if "成功" in values[2]:
                    success_count += 1
                elif "失败" in values[2]:
                    fail_count += 1

        self.exec_status_var.set(f"执行状态: 成功 {success_count} / 失败 {fail_count}")

    def _set_execution_state(self, is_executing):
        """设置执行状态并更新UI"""
        self.is_executing = is_executing

        # 更新按钮状态
        if is_executing:
            self.test_btn.config(state="disabled")
            self.execute_btn.config(state="disabled")
            self.stop_btn.config(state="normal")
        else:
            self.test_btn.config(state="normal")
            self.execute_btn.config(state="normal")
            self.stop_btn.config(state="disabled")

    def stop_execution(self):
        """停止当前执行"""
        if self.is_executing:
            self.cancel_event.set()
            if self.thread_pool:
                # 尝试取消未开始的任务
                try:
                    self.thread_pool.shutdown(wait=False)
                except:
                    pass
            self.progress_label.config(text="正在停止...")
            messagebox.showinfo("提示", "正在停止执行，请稍候...")

    def _on_config_window_close(self):
        """处理配置窗口关闭事件"""
        if hasattr(self, 'config_window'):
            self.config_window.destroy()
            del self.config_window

    def create_default_config(self):
        """创建默认配置"""
        self.database_configs = [
            {
                "db_app": "MySQL",
                "db_name": "station_gantry",
                "db_user": "root",
                "db_password": "kH7@S#2m"
            }
        ]
        self.db_types = ["MySQL"]
        self.db_app.set("MySQL")
        self.save_json_config()

    def save_json_config(self):
        """保存配置到JSON文件"""
        config_data = {
            "program_settings": self.program_settings,
            "database_configs": self.database_configs,
            "server_database_configs": self.server_database_configs
        }

        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(config_data, f, indent=4, ensure_ascii=False)
        except Exception as e:
            messagebox.showerror("错误", f"保存配置文件失败: {e}")


    def load_json_config(self):
        """从JSON文件加载配置"""
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    config_data = json.load(f)

                # 加载程序设置
                if 'program_settings' in config_data:
                    self.program_settings = config_data['program_settings']
                    self.max_threads = self.program_settings.get('max_threads', 10)
                    
                    # Populate self.script_files from default_script
                    default_script_content = self.program_settings.get('default_script', 'script.sql')
                    if default_script_content:
                        self.script_files = [f.strip() for f in default_script_content.split('\n') if f.strip()]
                    else:
                        self.script_files = [] # No default script

                    # Update main UI's script_file StringVar (for the Entry if it existed, now useful for internal tracking)
                    if self.script_files:
                        self.script_file.set("; ".join(self.script_files))
                    else:
                        self.script_file.set("script.sql") # Default placeholder if no default scripts

                    self.merge_single_row.set(self.program_settings.get('merge_result_enabled', False))
                    self.password_mask_enabled.set(self.program_settings.get('password_mask_enabled', False))
                    # 日志开关
                    self._setup_logging()

                    # 如果配置窗口已创建，更新默认脚本路径的ScrolledText
                    if hasattr(self, 'config_script_path') and self.config_script_path.winfo_exists():
                        self.config_script_path.config(state="normal")
                        self.config_script_path.delete(1.0, tk.END)
                        self.config_script_path.insert(tk.END, self.program_settings.get('default_script', ''))
                        self.config_script_path.config(state="disabled")

                # 加载数据库配置
                if 'database_configs' in config_data:
                    self.database_configs = config_data['database_configs']
                    # 提取数据库应用列表
                    self.db_types = [config.get('db_app', '') for config in self.database_configs]

                    # 如果有数据库配置，设置默认值
                    if self.database_configs:
                        default_config = self.database_configs[0]
                        self.db_app.set(default_config.get('db_app', ''))
                        self.db_name.set(default_config.get('db_name', 'test'))
                        self.db_user.set(default_config.get('db_user', 'root'))
                        self.db_password.set(default_config.get('db_password', ''))

                    # 更新数据库应用下拉框
                    if hasattr(self, 'db_type_combo'):
                        self.db_type_combo['values'] = self.db_types

                # 加载服务器源库配置
                if 'server_database_configs' in config_data:
                    self.server_database_configs = config_data['server_database_configs']
                    # 提取服务器源库应用列表
                    self.server_db_types = [config.get('db_app', '') for config in self.server_database_configs]

                    # 如果有服务器源库配置，设置默认值
                    if self.server_database_configs:
                        default_config = self.server_database_configs[0]
                        self.server_db_app.set(default_config.get('db_app', ''))
                        self.server_db_name.set(default_config.get('db_name', ''))
                        self.server_db_user.set(default_config.get('username', ''))
                        self.server_db_password.set(default_config.get('password', ''))

                    # 更新服务器源库应用下拉框
                    if hasattr(self, 'server_db_combo'):
                        self.server_db_combo['values'] = self.server_db_types
        except Exception as e:
            messagebox.showerror("错误", f"加载配置文件失败: {e}")
            self.logger.error(f"加载配置文件失败: {e}", exc_info=True)
            # 创建默认配置
            self.create_default_config()

    def create_default_config(self):
        """创建默认配置"""
        self.database_configs = [
            {
                "db_app": "MySQL",
                "db_name": "station_gantry",
                "db_user": "root",
                "db_password": "kH7@S#2m"
            }
        ]
        self.db_types = ["MySQL"]
        self.db_app.set("MySQL")
        self.save_json_config()

    def save_json_config(self):
        """保存配置到JSON文件"""
        config_data = {
            "program_settings": self.program_settings,
            "database_configs": self.database_configs,
            "server_database_configs": self.server_database_configs
        }

        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(config_data, f, indent=4, ensure_ascii=False)
        except Exception as e:
            messagebox.showerror("错误", f"保存配置文件失败: {e}")

    def save_server_json_config(self):
        """保存配置到JSON文件"""
        config_data = {
            "program_settings": self.program_settings,
            "database_configs": self.database_configs,
            "server_database_configs": self.server_database_configs
        }

        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(config_data, f, indent=4, ensure_ascii=False)
        except Exception as e:
            messagebox.showerror("错误", f"保存配置文件失败: {e}")

    def on_db_type_changed(self, event=None):
        """数据库应用变更事件"""
        selected_app = self.db_app.get()

        # 查找对应的数据库配置
        for config in self.database_configs:
            if config.get('db_app') == selected_app:
                self.db_name.set(config.get('db_name', ''))
                self.db_user.set(config.get('db_user', ''))
                self.db_password.set(config.get('db_password', ''))
                # print(config.get('db_name', ''), config.get('db_user', ''), config.get('db_password', ''))
                break

    def on_server_db_changed(self, event=None):
        """服务器源库变更事件"""
        selected_app = self.server_db_app.get()

        # 查找对应的服务器源库配置
        for config in self.server_database_configs:
            if config.get('db_app') == selected_app:
                self.server_db_type.set(config.get('db_type', ''))
                self.server_db_ip.set(config.get('db_ip', ''))
                self.server_db_name.set(config.get('db_name', ''))
                self.server_db_user.set(config.get('username', ''))
                self.server_db_password.set(config.get('password', ''))
                break

    def get_server_list(self):
        """从数据库获取服务器列表"""
        if self.is_executing:
            messagebox.showwarning("警告", "正在执行中，请先停止当前操作")
            return

        # 获取服务器源库配置
        db_app = self.server_db_app.get()
        db_type = self.server_db_type.get()
        db_ip = self.server_db_ip.get()
        db_name = self.server_db_name.get()
        db_user = self.server_db_user.get()
        db_password = self.server_db_password.get()
        list_sql = self.server_list_sql.get().strip()

        if not db_app or not db_name or not db_user:
            messagebox.showwarning("警告", "请先完善服务器源库配置信息")
            return

        if not list_sql:
            messagebox.showwarning("警告", "请输入获取列表SQL语句")
            return

        # 重置进度条
        self.progress_var.set(0)
        self.progress_label.config(text="正在获取服务器列表...")

        # 更新按钮状态
        self._set_execution_state(True)

        # 启动获取服务器列表线程
        threading.Thread(target=self._get_server_list_thread, args=(db_type, db_ip, db_name, db_user, db_password, list_sql), daemon=True).start()

    def _get_server_list_thread(self, db_type, db_ip, db_name, db_user, db_password, list_sql):
        """获取服务器列表线程"""
        try:
            # 根据数据库类型连接数据库
            if db_type == 'MySQL':
                import mysql.connector
                connection = mysql.connector.connect(
                    host=db_ip,
                    port=3306,
                    user=db_user,
                    password=db_password,
                    database=db_name,
                    connection_timeout=10
                )
            else:  # SQL Server
                connection = pymssql.connect(
                    server=db_ip,
                    port=1433,
                    user=db_user,
                    password=db_password,
                    database=db_name,
                    login_timeout=10
                )

            cursor = connection.cursor()

            # 执行SQL语句获取服务器列表
            cursor.execute(list_sql)

            # 获取结果
            results = cursor.fetchall()

            # 关闭连接
            cursor.close()
            connection.close()

            # 处理结果
            if results:
                server_list = []
                for row in results:
                    if row[0]:
                        server_list.append(row[0])

                # 更新服务器列表文本框
                self.root.after(0, self._update_server_list, "\n".join(server_list))
            else:
                self.root.after(0, messagebox.showinfo, "提示", "未找到服务器列表")

        except Exception as e:
            self.root.after(0, messagebox.showerror, "错误", f"获取服务器列表失败: {e}")
        finally:
            self.root.after(0, self._update_progress, 100, "获取完成")
            self.root.after(0, self._set_execution_state, False)

    def _update_server_list(self, server_list):
        """更新服务器列表文本框"""
        # 清空文本框
        self.server_text.delete(1.0, tk.END)

        # 添加服务器列表
        if server_list:
            self.server_text.insert(tk.END, server_list)
            messagebox.showinfo("提示", "服务器列表获取成功")
        else:
            messagebox.showinfo("提示", "未找到服务器列表")

    def create_default_result_tab(self):
        """创建默认的无连接标签页"""
        # 创建默认标签页框架
        default_frame = ttk.Frame(self.result_notebook)
        self.result_notebook.add(default_frame, text="无连接")

        # 创建表格显示SQL执行结果
        self.create_result_table(default_frame)

    def create_result_table(self, parent):
        """创建结果表格"""
        # 配置父容器的权重，使其内部的table_frame可以扩展
        parent.columnconfigure(0, weight=1)
        parent.rowconfigure(0, weight=1)

        # 创建表格框架
        table_frame = ttk.Frame(parent)
        table_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
        
        # 创建Treeview表格
        self.result_tree = ttk.Treeview(table_frame, show='headings')
        
        # 添加垂直滚动条
        v_scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=self.result_tree.yview)
        self.result_tree.configure(yscrollcommand=v_scrollbar.set)
        
        # 添加水平滚动条
        h_scrollbar = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=self.result_tree.xview)
        self.result_tree.configure(xscrollcommand=h_scrollbar.set)
        
        # 布局表格和滚动条
        self.result_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        v_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        h_scrollbar.grid(row=1, column=0, sticky=(tk.W, tk.E))
        
        # 配置表格框架的权重
        table_frame.columnconfigure(0, weight=1)
        table_frame.rowconfigure(0, weight=1)

    def update_result_tabs(self, server_ips):
        """根据服务器IP列表更新结果标签页"""
        # 清除所有现有标签页
        for tab in self.result_notebook.tabs():
            self.result_notebook.forget(tab)

        # 如果没有服务器IP，创建默认标签页
        if not server_ips:
            self.create_default_result_tab()
            return

        # 为每个服务器IP创建标签页
        for ip in server_ips:
            frame = ttk.Frame(self.result_notebook)
            self.result_notebook.add(frame, text=ip)
            self.create_result_table(frame)

    def display_merged_sql_results(self, results_dict, failed_servers=None):
        """合并所有服务器的所有结果到一个表格，最前列为IP，列宽自适应，按服务器列表顺序"""
        # 清空主结果表格
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)

        # 只保留合并统计标签页
        for tab in self.result_notebook.tabs():
            self.result_notebook.forget(tab)
        frame = ttk.Frame(self.result_notebook)
        self.result_notebook.add(frame, text="合并结果")

        # The final merged columns will always be fixed for this view
        merged_columns_final = ["服务器IP", "序号", "合并结果"]
        merged_rows_final = []
        
        # Get server order for consistent display
        server_order = self.parse_servers()
        success_count = 0
        fail_count = 0

        for host, port in server_order:
            server_key = f"{host}:{port}"
            result_data = results_dict.get(server_key)

            if result_data: # If execution was successful for this server
                columns_from_single_sql, rows_from_single_sql = result_data
                
                # Check if this result block was already internally merged (multiple SELECTs case)
                if columns_from_single_sql == ["序号", "合并结果"]:
                    # Already internally merged, just add server IP
                    for row_data in rows_from_single_sql: # row_data is [sequence_number, merged_row_string]
                        merged_rows_final.append([server_key] + row_data)
                else:
                    # This result was from a single SELECT, need to internally merge it for the final merged view
                    if rows_from_single_sql: # Only process if there are rows
                        sequence_number = 1 # Always 1 for a single SELECT in this context
                        for original_row in rows_from_single_sql:
                            merged_field_data = ", ".join(str(cell) for cell in original_row)
                            merged_rows_final.append([server_key, sequence_number, merged_field_data])
                    # If rows_from_single_sql is empty, it means single SELECT returned no results, still a success
                success_count += 1
            else:
                # Execution failed for this server
                fail_count += 1
                # Add to main summary treeview directly for failed servers
                self.results_tree.insert('', tk.END, values=(host, port, "执行失败", "-", "执行失败或无结果"))

        # 如果有额外的失败服务器信息（从failed_servers参数），也添加到结果摘要中
        if failed_servers:
            for host, port, error_msg in failed_servers:
                # 检查是否已经在results_tree中
                if not any(item_values[0] == host and item_values[1] == str(port) for item_values in [self.results_tree.item(item, 'values') for item in self.results_tree.get_children()]):
                    self.results_tree.insert('', tk.END, values=(host, port, "执行失败", "-", error_msg))

        # Calculate column widths for the final merged treeview
        col_widths = [len(str(col)) for col in merged_columns_final]
        for row in merged_rows_final:
            for i, cell in enumerate(row):
                col_widths[i] = max(col_widths[i], len(str(cell)))

        min_px = 40
        max_px = 500

        px_widths = []
        for i, len_ in enumerate(col_widths):
            if merged_columns_final[i] == "服务器IP":
                px_widths.append(min(max(len_ * 6 + 20, 100), max_px))
            elif merged_columns_final[i] == "序号":
                px_widths.append(min(max(len_ * 6 + 20, min_px), 80))
            else: # "合并结果" column
                px_widths.append(min(max(len_ * 6 + 20, min_px), max_px))
        
        # Create the merged table in the "合并结果" tab
        table_frame = ttk.Frame(frame)
        table_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
        frame.columnconfigure(0, weight=1)
        frame.rowconfigure(0, weight=1)

        tree = ttk.Treeview(table_frame, columns=merged_columns_final, show='headings')
        for i, col in enumerate(merged_columns_final):
            tree.heading(col, text=col)
            tree.column(col, width=px_widths[i], minwidth=40, stretch=tk.NO)
        for row in merged_rows_final:
            tree.insert('', tk.END, values=row)

        v_scrollbar = ttk.Scrollbar(table_frame, orient=tk.VERTICAL, command=tree.yview)
        tree.configure(yscrollcommand=v_scrollbar.set)
        h_scrollbar = ttk.Scrollbar(table_frame, orient=tk.HORIZONTAL, command=tree.xview)
        tree.configure(xscrollcommand=h_scrollbar.set)

        tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        v_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        h_scrollbar.grid(row=1, column=0, sticky=(tk.W, tk.E))
        table_frame.columnconfigure(0, weight=1)
        table_frame.rowconfigure(0, weight=1)

        # Update main results summary table (success/fail count)
        self.exec_status_var.set(f"执行状态: 成功 {success_count} / 失败 {fail_count}")

        # 确保所有服务器都在结果摘要中显示
        # 重新填充主结果树，确保包含所有服务器（成功和失败的）
        for item in self.results_tree.get_children():
            self.results_tree.delete(item)
        
        for host, port in server_order:
            server_key = f"{host}:{port}"
            if results_dict.get(server_key) is not None: # Successfully processed
                self.results_tree.insert('', tk.END, values=(host, port, '执行成功', '-', ''))
            else: # Failed
                # 查找具体的错误信息
                error_msg = "执行失败或无结果"
                if failed_servers:
                    for failed_host, failed_port, failed_error in failed_servers:
                        if failed_host == host and str(failed_port) == str(port):
                            error_msg = failed_error
                            break
                self.results_tree.insert('', tk.END, values=(host, port, '执行失败', '-', error_msg))

    def move_db_config_up(self):
        """向上移动选中的数据库配置项"""
        selected_items = self.db_config_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要移动的配置项")
            return

        # 遍历选中项，从上往下处理，确保顺序正确
        for item_id in selected_items:
            index = self.db_config_tree.index(item_id)
            if index > 0:
                # 在Treeview中移动
                self.db_config_tree.move(item_id, self.db_config_tree.parent(item_id), index - 1)
                # 在列表中移动
                config_values = self.db_config_tree.item(item_id, 'values')
                # Find the actual config dict in self.database_configs
                for i, config in enumerate(self.database_configs):
                    # Compare based on unique fields (db_app, db_name, db_user)
                    if (config.get('db_app') == config_values[0] and
                        config.get('db_name') == config_values[1] and
                        config.get('db_user') == config_values[2]): # Removed password from comparison for robustness
                        
                        moved_config = self.database_configs.pop(i)
                        self.database_configs.insert(i - 1, moved_config)
                        break
        self.save_json_config()
        self.load_db_config_to_tree() # Reload to ensure correct order and password masking

    def move_db_config_down(self):
        """向下移动选中的数据库配置项"""
        selected_items = self.db_config_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要移动的配置项")
            return

        # 遍历选中项，从下往上处理，确保顺序正确
        for item_id in reversed(selected_items):
            index = self.db_config_tree.index(item_id)
            if index < len(self.db_config_tree.get_children()): # Removed -0, it's redundant and potentially buggy
                # 在Treeview中移动
                self.db_config_tree.move(item_id, self.db_config_tree.parent(item_id), index + 1)
                # 在列表中移动
                config_values = self.db_config_tree.item(item_id, 'values')
                # Find the actual config dict in self.database_configs
                for i, config in enumerate(self.database_configs):
                    # Compare based on unique fields (db_app, db_name, db_user)
                    if (config.get('db_app') == config_values[0] and
                        config.get('db_name') == config_values[1] and
                        config.get('db_user') == config_values[2]): # Removed password from comparison for robustness

                        moved_config = self.database_configs.pop(i)
                        self.database_configs.insert(i + 1, moved_config)
                        break
        self.save_json_config()
        self.load_db_config_to_tree() # Reload to ensure correct order and password masking

    def move_server_db_config_up(self):
        """向上移动选中的服务器源库配置项"""
        selected_items = self.server_db_config_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要移动的配置项")
            return

        # 遍历选中项，从上往下处理，确保顺序正确
        for item_id in selected_items:
            index = self.server_db_config_tree.index(item_id)
            if index > 0:
                # 在Treeview中移动
                self.server_db_config_tree.move(item_id, self.server_db_config_tree.parent(item_id), index - 1)
                # 在列表中移动
                config_values = self.server_db_config_tree.item(item_id, 'values')
                # Find the actual config dict in self.server_database_configs
                for i, config in enumerate(self.server_database_configs):
                    # Compare based on unique fields (db_app, db_type, db_ip, db_name, username)
                    if (config.get('db_app', '').strip() == config_values[0].strip() and
                        config.get('db_type', '').strip() == config_values[1].strip() and
                        config.get('db_ip', '').strip() == config_values[2].strip() and
                        config.get('db_name', '').strip() == config_values[3].strip() and
                        config.get('username', '').strip() == config_values[4].strip()):
                            
                        moved_config = self.server_database_configs.pop(i)
                        self.server_database_configs.insert(i - 1, moved_config)
                        break
        self.save_json_config()
        self.load_server_db_config_to_tree() # Reload to ensure correct order and password masking

    def move_server_db_config_down(self):
        """向下移动选中的服务器源库配置项"""
        selected_items = self.server_db_config_tree.selection()
        if not selected_items:
            messagebox.showwarning("警告", "请先选择要移动的配置项")
            return

        # 遍历选中项，从下往上处理，确保顺序正确
        for item_id in reversed(selected_items):
            index = self.server_db_config_tree.index(item_id)
            if index < len(self.server_db_config_tree.get_children()):
                # 在Treeview中移动
                self.server_db_config_tree.move(item_id, self.server_db_config_tree.parent(item_id), index + 1)
                # 在列表中移动
                config_values = self.server_db_config_tree.item(item_id, 'values')
                # Find the actual config dict in self.server_database_configs
                for i, config in enumerate(self.server_database_configs):
                    # Compare based on unique fields (db_app, db_type, db_ip, db_name, username)
                    if (config.get('db_app', '').strip() == config_values[0].strip() and
                        config.get('db_type', '').strip() == config_values[1].strip() and
                        config.get('db_ip', '').strip() == config_values[2].strip() and
                        config.get('db_name', '').strip() == config_values[3].strip() and
                        config.get('username', '').strip() == config_values[4].strip()):

                        moved_config = self.server_database_configs.pop(i)
                        self.server_database_configs.insert(i + 1, moved_config)
                        break
        self.save_json_config()
        self.load_server_db_config_to_tree() # Reload to ensure correct order and password masking

    def browse_save_excel_path(self):
        """浏览保存Excel文件路径（选择文件名）"""
        path = filedialog.asksaveasfilename(
            defaultextension='.xlsx',
            filetypes=[('Excel文件', '*.xlsx')],
            title='保存为Excel文件'
        )
        if path:
            self.save_excel_path_var.set(path)

    def save_sql_results_to_excel(self):
        """保存SQL执行结果到Excel"""
        save_path = self.save_excel_path_var.get()
        if not save_path:
            save_path = filedialog.asksaveasfilename(
                defaultextension='.xlsx',
                filetypes=[('Excel文件', '*.xlsx')],
                title='保存为Excel文件'
            )
        if not save_path:
            return # 用户取消了保存
        if not save_path.endswith('.xlsx'):
            save_path += '.xlsx'

        wb = Workbook()
        if 'Sheet' in wb.sheetnames:
            wb.remove(wb['Sheet']) # 移除默认创建的空工作表

        merge_mode = self.merge_single_row.get()

        if merge_mode:
            # 合并模式：查找名为“合并结果”的标签页并保存
            merged_tab_id = None
            for tab_id in self.result_notebook.tabs():
                if self.result_notebook.tab(tab_id, 'text') == '合并结果':
                    merged_tab_id = tab_id
                    break
            
            if not merged_tab_id:
                messagebox.showerror("错误", "未找到'合并结果'标签页。")
                return

            frame = self.result_notebook.nametowidget(merged_tab_id)
            tree = None
            table_frame = None
            for widget in frame.winfo_children():
                if isinstance(widget, ttk.Frame):
                    table_frame = widget
                    break
            
            if table_frame:
                for widget in table_frame.winfo_children():
                    if isinstance(widget, ttk.Treeview):
                        tree = widget
                        break
            
            if tree is None:
                messagebox.showerror("错误", "在'合并结果'标签页中未找到结果表格。")
                return

            ws = wb.create_sheet(title='合并结果')
            # columns = list(tree.cget('columns'))
            # if columns:
            #     ws.append(['IP'] + columns)

            for row_id in tree.get_children():
                ip = tree.item(row_id, 'text')
                values = tree.item(row_id, 'values')
                if not isinstance(values, (list, tuple)):
                    values = [values] if values else []
                ws.append(list(values))
        else:
            # 非合并模式：每个服务器一个工作表
            if not self.result_notebook.tabs():
                 messagebox.showinfo("提示", "没有可保存的结果。")
                 return

            for tab_id in self.result_notebook.tabs():
                tab_text = self.result_notebook.tab(tab_id, 'text')
                if tab_text == '合并结果': # 非合并模式下跳过此页
                    continue

                frame = self.result_notebook.nametowidget(tab_id)
                tree = None
                # 修正：同样需要在Frame中查找Treeview
                table_frame = None
                for widget in frame.winfo_children():
                    if isinstance(widget, ttk.Frame):
                        table_frame = widget
                        break
                
                if table_frame:
                    for widget in table_frame.winfo_children():
                        if isinstance(widget, ttk.Treeview):
                            tree = widget
                            break
                
                if tree is None:
                    continue

                ws = wb.create_sheet(title=tab_text)
                columns = list(tree.cget('columns'))
                if columns:
                    ws.append(columns)
                
                for row_id in tree.get_children():
                    values = tree.item(row_id, 'values')
                    if not isinstance(values, (list, tuple)):
                        values = [values] if values else []
                    if values:
                        ws.append(list(values))

        if not wb.sheetnames:
            messagebox.showinfo("提示", "没有可保存的数据。")
            return

        try:
            wb.save(save_path)
            messagebox.showinfo("提示", f"SQL执行结果已保存到: {save_path}")
        except Exception as e:
            messagebox.showerror("错误", f"保存Excel文件失败: {e}")

def main():
    root = tk.Tk()
    app = MySQLGUITool(root)
    root.mainloop()

if __name__ == "__main__":
    main()